File Input Output Java

/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */
// Diff -- text file difference utility.
// See full docu-comment at beginning of Diff class.
// $Id: Diff.java,v 1.6 2004/02/09 03:34:05 ian Exp $
import java.io.*;
/** This is the info kept per-file.     */
class fileInfo {    
  static final int MAXLINECOUNT = 20000;
  DataInputStream file;  /* File handle that is open for read.  */
  public int maxLine;  /* After input done, # lines in file.  */
  node symbol[]; /* The symtab handle of each line. */
  int other[]; /* Map of line# to line# in other file */
                                /* ( -1 means don't-know ).            */
        /* Allocated AFTER the lines are read. */
  /**
   * Normal constructor with one filename; file is opened and saved.
   */
  fileInfo( String filename ) {
    symbol = new node [ MAXLINECOUNT+2 ];
    other  = null;    // allocated later!
    try {
      file = new DataInputStream(
        new FileInputStream( filename));
    } catch (IOException e) {
        System.err.println("Diff can't read file " +
        filename );
        System.err.println("Error Exception was:" + e );
        System.exit(1);
    }
  }
  // This is done late, to be same size as # lines in input file.
  void alloc() {
    other  = new int[symbol.length + 2];
  }
};
/**
 * diff         Text file difference utility.
 * ----         Copyright 1987, 1989 by Donald C. Lindsay,
 *              School of Computer Science,  Carnegie Mellon University.
 *              Copyright 1982 by Symbionics.
 *              Use without fee is permitted when not for direct commercial
 *              advantage, and when credit to the source is given. Other uses
 *              require specific permission.
 *
 * Converted from C to Java by Ian F. Darwin, http://www.darwinsys.com/, January, 1997.
 * Copyright 1997, Ian F. Darwin.
 *
 * Conversion is NOT FULLY TESTED.
 *
 * USAGE:      diff oldfile newfile
 *
 * This program assumes that "oldfile" and "newfile" are text files.
 * The program writes to stdout a description of the changes which would
 * transform "oldfile" into "newfile".
 *
 * The printout is in the form of commands, each followed by a block of
 * text. The text is delimited by the commands, which are:
 *
 *    DELETE AT n
 *         ..deleted lines
 *
 *    INSERT BEFORE n
 *         ..inserted lines
 *
 *    n MOVED TO BEFORE n
 *         ..moved lines
 *
 *    n CHANGED FROM
 *         ..old lines
 *    CHANGED TO
 *         ..newer lines
 *
 * The line numbers all refer to the lines of the oldfile, as they are
 *    numbered before any commands are applied.
 * The text lines are printed as-is, without indentation or prefixing. The
 *    commands are printed in upper case, with a prefix of ">>>>", so that
 *    they will stand out. Other schemes may be preferred.
 * Files which contain more than MAXLINECOUNT lines cannot be processed.
 *    This can be fixed by changing "symbol" to a Vector.
 * The algorithm is taken from Communications of the ACM, Apr78 (21, 4, 264-),
 *    "A Technique for Isolating Differences Between Files."
 *    Ignoring I/O, and ignoring the symbol table, it should take O(N) time.
 *    This implementation takes fixed space, plus O(U) space for the symbol
 *    table (where U is the number of unique lines). Methods exist to change
 *    the fixed space to O(N) space.
 * Note that this is not the only interesting file-difference algorithm. In
 *    general, different algorithms draw different conclusions about the
 *    changes that have been made to the oldfile. This algorithm is sometimes
 *    "more right", particularly since it does not consider a block move to be 
 *    an insertion and a (separate) deletion. However, on some files it will be
 *    "less right". This is a consequence of the fact that files may contain
 *    many identical lines (particularly if they are program source). Each
 *    algorithm resolves the ambiguity in its own way, and the resolution
 *    is never guaranteed to be "right". However, it is often excellent.
 * This program is intended to be pedagogic.  Specifically, this program was
 *    the basis of the Literate Programming column which appeared in the
 *    Communications of the ACM (CACM), in the June 1989 issue (32, 6,
 *    740-755).
 * By "pedagogic", I do not mean that the program is gracefully worded, or
 *    that it showcases language features or its algorithm. I also do not mean
 *    that it is highly accessible to beginners, or that it is intended to be
 *    read in full, or in a particular order. Rather, this program is an
 *    example of one professional's style of keeping things organized and
 *    maintainable.
 * The program would be better if the "print" variables were wrapped into
 *    a struct. In general, grouping related variables in this way improves
 *    documentation, and adds the ability to pass the group in argument lists.
 * This program is a de-engineered version of a program which uses less
 *    memory and less time.  The article points out that the "symbol" arrays
 *    can be implemented as arrays of pointers to arrays, with dynamic
 *    allocation of the subarrays.  (In C, macros are very useful for hiding 
 *    the two-level accesses.) In Java, a Vector would be used. This allows an
 *    extremely large value for MAXLINECOUNT, without dedicating fixed arrays.
 *    (The "other" array can be allocated after the input phase, when the exact
 *    sizes are known.) The only slow piece of code is the "strcmp" in the tree
 *    descent: it can be speeded up by keeping a hash in the tree node, and
 *    only using "strcmp" when two hashes happen to be equal.
 *
 * Change Log
 * ----------
 *  1Jan97 Ian F. Darwin: first working rewrite in Java, based entirely on
 *   D.C.Lindsay's reasonable C version.
 *  Changed comments from /***************** to /**, shortened, added
 *  whitespace, used tabs more, etc.
 *  6jul89 D.C.Lindsay, CMU: fixed portability bug. Thanks, Gregg Wonderly.
 *         Just changed "char ch" to "int ch". 
 *         Also added comment about way to improve code.
 * 10jun89 D.C.Lindsay, CMU: posted version created.
 *         Copyright notice changed to ACM style, and Dept. is now School.
 *         ACM article referenced in docn.
 * 26sep87 D.C.Lindsay, CMU: publication version created.
 *         Condensed all 1982/83 change log entries.
 *         Removed all command line options, and supporting code. This 
 *         simplified the input code (no case reduction etc). It also
 *         simplified the symbol table, which was capable of remembering
 *         offsets into files (instead of strings), and trusting (!) hash
 *         values to be unique.
 *         Removed dynamic allocation of arrays: now fixed static arrays.
 *         Removed speed optimizations in symtab package.
 *         Removed string compression/decompression code.
 *         Recoded to Unix standards from old Lattice/MSDOS standards.
 *         (This affected only the #include's and the IO.)
 *         Some renaming of variables, and rewording of comments.
 * 1982/83 D.C.Lindsay, Symbionics: created.
 *
 * @author  Ian F. Darwin, Java version
 * @version  Java version 0.9, 1997
 * @author  D. C. Lindsay, C version (1982-1987)
 *
 */
public class Diff {
  /** block len > any possible real block len */
  final int UNREAL=Integer.MAX_VALUE;
  /** Keeps track of information about file1 and file2 */
  fileInfo oldinfo, newinfo;
  /** blocklen is the info about found blocks. It will be set to 0, except
   * at the line#s where blocks start in the old file. At these places it
   * will be set to the # of lines in the block. During printout ,
   * this # will be reset to -1 if the block is printed as a MOVE block
   * (because the printout phase will encounter the block twice, but
   * must only print it once.)
   * The array declarations are to MAXLINECOUNT+2 so that we can have two
   * extra lines (pseudolines) at line# 0 and line# MAXLINECOUNT+1
   * (or less).
   */
  int blocklen[];
  /**
   * main - entry point when used standalone.
   * NOTE: no routines return error codes or throw any local
   * exceptions. Instead, any routine may complain
   * to stderr and then exit with error to the system.
   */
  public static void main(String argstrings[])
  {
    if ( argstrings.length != 2 ) {
      System.err.println("Usage: diff oldfile newfile" );
      System.exit(1);
    }
    Diff d = new Diff();
    d.doDiff(argstrings[0], argstrings[1]);
    return;
  }
  /** Construct a Diff object. */
  Diff() {
  }
  /** Do one file comparison. Called with both filenames. */
  public void doDiff(String oldFile, String newFile) {
    println( ">>>> Difference of file \"" + oldFile + 
      "\" and file \"" + newFile + "\".\n");
    oldinfo = new fileInfo(oldFile);
    newinfo = new fileInfo(newFile);
    /* we don't process until we know both files really do exist. */
    try {
      inputscan( oldinfo );
      inputscan( newinfo );
    } catch (IOException e) {
      System.err.println("Read error: " + e);
    }
    /* Now that we've read all the lines, allocate some arrays.
     */
    blocklen = new int[ (oldinfo.maxLine>newinfo.maxLine?
      oldinfo.maxLine : newinfo.maxLine) + 2 ];
    oldinfo.alloc();
    newinfo.alloc();
    /* Now do the work, and print the results. */
    transform();
    printout();
  }
  /**
   * inputscan    Reads the file specified by pinfo.file.
   * ---------    Places the lines of that file in the symbol table.
   *              Sets pinfo.maxLine to the number of lines found.
   */
  void inputscan( fileInfo pinfo ) throws IOException
  {
       String linebuffer;
       pinfo.maxLine = 0;
       while ((linebuffer = pinfo.file.readLine()) != null) {
           storeline( linebuffer, pinfo );
       }
  }
  /**
   * storeline    Places line into symbol table.
   * ---------    Expects pinfo.maxLine initted: increments.
   *              Places symbol table handle in pinfo.ymbol.
   *              Expects pinfo is either oldinfo or newinfo.
   */
  void storeline( String linebuffer, fileInfo pinfo )
  {
       int linenum = ++pinfo.maxLine;    /* note, no line zero */
       if ( linenum > fileInfo.MAXLINECOUNT ) {
      System.err.println( "MAXLINECOUNT exceeded, must stop." );
      System.exit(1);
       }
       pinfo.symbol[ linenum ] =
      node.addSymbol( linebuffer, pinfo == oldinfo, linenum );
  }
  /*
   * transform    
   * Analyzes the file differences and leaves its findings in
   * the global arrays oldinfo.other, newinfo.other, and blocklen.
   * Expects both files in symtab.
   * Expects valid "maxLine" and "symbol" in oldinfo and newinfo.
   */
  void transform()
  {                                  
       int oldline, newline;
       int oldmax = oldinfo.maxLine + 2;  /* Count pseudolines at  */
       int newmax = newinfo.maxLine + 2;  /* ..front and rear of file */
       for (oldline=0; oldline < oldmax; oldline++ )
      oldinfo.other[oldline]= -1;
       for (newline=0; newline < newmax; newline++ )
      newinfo.other[newline]= -1;
       scanunique();  /* scan for lines used once in both files */
       scanafter();   /* scan past sure-matches for non-unique blocks */
       scanbefore();  /* scan backwards from sure-matches */
       scanblocks();  /* find the fronts and lengths of blocks */
  }
  /*
   * scanunique
   * Scans for lines which are used exactly once in each file.
   * Expects both files in symtab, and oldinfo and newinfo valid.
   * The appropriate "other" array entries are set to the line# in
   * the other file.
   * Claims pseudo-lines at 0 and XXXinfo.maxLine+1 are unique.
   */
  void scanunique()
  {
       int oldline, newline;
       node psymbol;
       for( newline = 1; newline <= newinfo.maxLine; newline++ ) {
      psymbol = newinfo.symbol[ newline ];
      if ( psymbol.symbolIsUnique()) {        // 1 use in each file
           oldline = psymbol.linenum;
           newinfo.other[ newline ] = oldline; // record 1-1 map
           oldinfo.other[ oldline ] = newline;
      }
       }
       newinfo.other[ 0 ] = 0;
       oldinfo.other[ 0 ] = 0;
       newinfo.other[ newinfo.maxLine + 1 ] = oldinfo.maxLine + 1;
       oldinfo.other[ oldinfo.maxLine + 1 ] = newinfo.maxLine + 1;
  }
  /*
   * scanafter
   * Expects both files in symtab, and oldinfo and newinfo valid.
   * Expects the "other" arrays contain positive #s to indicate
   * lines that are unique in both files.
   * For each such pair of places, scans past in each file.
   * Contiguous groups of lines that match non-uniquely are
   * taken to be good-enough matches, and so marked in "other".
   * Assumes each other[0] is 0.
   */
  void scanafter()
  {
       int oldline, newline;
       for( newline = 0; newline <= newinfo.maxLine; newline++ ) {
      oldline = newinfo.other[ newline ];
      if ( oldline >= 0 ) {  /* is unique in old & new */
           for(;;) {  /* scan after there in both files */
          if ( ++oldline > oldinfo.maxLine   ) break; 
          if ( oldinfo.other[ oldline ] >= 0 ) break;
          if ( ++newline > newinfo.maxLine   ) break; 
          if ( newinfo.other[ newline ] >= 0 ) break;
          /* oldline & newline exist, and 
        aren't already matched */
          if ( newinfo.symbol[ newline ] !=
        oldinfo.symbol[ oldline ] ) break;  // not same
          newinfo.other[newline] = oldline; // record a match
          oldinfo.other[oldline] = newline;
           }
      }
       }
  }
  /**
   * scanbefore
   * As scanafter, except scans towards file fronts.
   * Assumes the off-end lines have been marked as a match.
   */
  void scanbefore()
  {
       int oldline, newline;
       for( newline = newinfo.maxLine + 1; newline > 0; newline-- ) {
      oldline = newinfo.other[ newline ];
      if ( oldline >= 0 ) {               /* unique in each */
           for(;;) {
          if ( --oldline <= 0                ) break;
          if ( oldinfo.other[ oldline ] >= 0 ) break;
          if ( --newline <= 0                ) break;
          if ( newinfo.other[ newline ] >= 0 ) break;
     
          /* oldline and newline exist,
        and aren't marked yet */
          if ( newinfo.symbol[ newline ] !=
        oldinfo.symbol[ oldline ] ) break;  // not same
          newinfo.other[newline] = oldline; // record a match
          oldinfo.other[oldline] = newline;
           }
      }
       }
  }
  /**
   * scanblocks - Finds the beginnings and lengths of blocks of matches.
   * Sets the blocklen array (see definition).
   * Expects oldinfo valid.
   */
  void scanblocks()
  {
       int oldline, newline;
       int oldfront = 0;      // line# of front of a block in old, or 0 
       int newlast = -1;      // newline's value during prev. iteration
       for( oldline = 1; oldline <= oldinfo.maxLine; oldline++ )
           blocklen[ oldline ] = 0;
       blocklen[ oldinfo.maxLine + 1 ] = UNREAL; // starts a mythical blk
       for( oldline = 1; oldline <= oldinfo.maxLine; oldline++ ) {
      newline = oldinfo.other[ oldline ];
      if ( newline < 0 ) oldfront = 0;  /* no match: not in block */
      else{                                   /* match. */
           if ( oldfront == 0 )         oldfront = oldline;
           if ( newline != (newlast+1)) oldfront = oldline;
           ++blocklen[ oldfront ];            
      }
      newlast = newline;
       }
  }
  /* The following are global to printout's subsidiary routines */
  // enum{ idle, delete, insert, movenew, moveold,
  // same, change } printstatus;
  public static final int
    idle = 0, delete = 1, insert = 2, movenew = 3, moveold = 4,
    same = 5, change = 6;
  int printstatus;
  boolean anyprinted;
  int printoldline, printnewline;     // line numbers in old & new file
  /**
   * printout - Prints summary to stdout.
   * Expects all data structures have been filled out.
   */
  void printout()
  {
       printstatus = idle;
       anyprinted = false;
       for( printoldline = printnewline = 1; ; ) {
      if ( printoldline > oldinfo.maxLine ) { newconsume(); break;}
      if ( printnewline > newinfo.maxLine ) { oldconsume(); break;}
      if (      newinfo.other[ printnewline ] < 0 ) {
           if ( oldinfo.other[ printoldline ] < 0 )
        showchange();
           else
        showinsert();
      }
      else if ( oldinfo.other[ printoldline ] < 0 )
      showdelete();
      else if ( blocklen[ printoldline ] < 0 )
      skipold();
      else if ( oldinfo.other[ printoldline ] == printnewline )
      showsame();
      else
      showmove();
       }
       if ( anyprinted == true ) println( ">>>> End of differences."  );
       else                     println( ">>>> Files are identical." );
  }
  /*
   * newconsume        Part of printout. Have run out of old file. 
   * Print the rest of the new file, as inserts and/or moves.
   */
  void newconsume()
  {
       for(;;) {
      if ( printnewline > newinfo.maxLine )
      break;        /* end of file */
      if ( newinfo.other[ printnewline ] < 0 ) showinsert();
      else                                    showmove();
       }
  }
  /**
   * oldconsume        Part of printout. Have run out of new file.
   * Process the rest of the old file, printing any
   * parts which were deletes or moves.
   */
  void oldconsume()
  {
       for(;;) {
      if ( printoldline > oldinfo.maxLine )
      break;       /* end of file */
      printnewline = oldinfo.other[ printoldline ];
      if ( printnewline < 0 ) showdelete();
      else if ( blocklen[ printoldline ] < 0 ) skipold();
      else showmove();
       }
  }
  /**
   * showdelete        Part of printout.
   * Expects printoldline is at a deletion.
   */
  void showdelete()
  {
    if ( printstatus != delete )
      println( ">>>> DELETE AT " + printoldline);
    printstatus = delete;
    oldinfo.symbol[ printoldline ].showSymbol();
    anyprinted = true;
    printoldline++;
  }
  /*
   * showinsert        Part of printout.
   * Expects printnewline is at an insertion.
   */
  void showinsert()
  {
       if ( printstatus == change ) println( ">>>>     CHANGED TO" );
       else if ( printstatus != insert ) 
      println( ">>>> INSERT BEFORE " + printoldline );
       printstatus = insert;
       newinfo.symbol[ printnewline ].showSymbol();
       anyprinted = true;
       printnewline++;
  }
  /**
   * showchange        Part of printout.
   * Expects printnewline is an insertion.
   *  Expects printoldline is a deletion.
   */
  void showchange()
  {
       if ( printstatus != change ) 
      println( ">>>> " + printoldline + " CHANGED FROM");
       printstatus = change;
       oldinfo.symbol[ printoldline ].showSymbol();
       anyprinted = true;
       printoldline++;
  }
  /**
   * skipold           Part of printout.
   * Expects printoldline at start of an old block that has 
   * already been announced as a move.
   * Skips over the old block.
   */
  void skipold()
  {
       printstatus = idle;
       for(;;) {
      if ( ++printoldline > oldinfo.maxLine )
      break;     /* end of file  */
      if ( oldinfo.other[ printoldline ] < 0 )
      break;    /* end of block */
      if ( blocklen[ printoldline ]!=0)
      break;          /* start of another */
       }
  }
  /**
   * skipnew           Part of printout.
   * Expects printnewline is at start of a new block that has
   * already been announced as a move.
   * Skips over the new block.
   */
  void skipnew()
  {
       int oldline;
       printstatus = idle;
       for(;;) {
      if ( ++printnewline > newinfo.maxLine )
      break;    /* end of file  */
      oldline = newinfo.other[ printnewline ];
      if ( oldline < 0 )
      break;                         /* end of block */
      if ( blocklen[ oldline ] != 0)
      break;              /* start of another */
       }
  }
  /**
   * showsame          Part of printout.
   * Expects printnewline and printoldline at start of
   * two blocks that aren't to be displayed.
   */
  void showsame()
  {
       int count;
       printstatus = idle;
       if ( newinfo.other[ printnewline ] != printoldline ) {
      System.err.println("BUG IN LINE REFERENCING");
      System.exit(1);
       }
       count = blocklen[ printoldline ];
       printoldline += count;
       printnewline += count;
  }
  /**
   * showmove          Part of printout.
   * Expects printoldline, printnewline at start of
   * two different blocks ( a move was done).
   */
  void showmove()
  {
       int oldblock = blocklen[ printoldline ];
       int newother = newinfo.other[ printnewline ];
       int newblock = blocklen[ newother ];
       if ( newblock < 0 ) skipnew();         // already printed.
       else if ( oldblock >= newblock ) {     // assume new's blk moved.
      blocklen[newother] = -1;         // stamp block as "printed".
      println( ">>>> " + newother + 
      " THRU " + (newother + newblock - 1) + 
      " MOVED TO BEFORE " + printoldline );
      for( ; newblock > 0; newblock--, printnewline++ )
           newinfo.symbol[ printnewline ].showSymbol();
      anyprinted = true;
      printstatus = idle;
       } else                /* assume old's block moved */
      skipold();      /* target line# not known, display later */
  }
  /** Convenience wrapper for println */
  public void println(String s) {
    System.out.println(s);
  }
};        // end of main class!
/**
 * Class "node". The symbol table routines in this class all
 * understand the symbol table format, which is a binary tree.
 * The methods are: addSymbol, symbolIsUnique, showSymbol.
 */ 
class node{                       /* the tree is made up of these nodes */
  node pleft, pright;
  int linenum;
  static final int freshnode = 0,
  oldonce = 1, newonce = 2, bothonce = 3, other = 4;
  int /* enum linestates */ linestate;
  String line;
  static node panchor = null;    /* symtab is a tree hung from this */
  /**
   * Construct a new symbol table node and fill in its fields.
   * @param        string  A line of the text file
   */
  node( String pline)
  {
       pleft = pright = null;
       linestate = freshnode;
       /* linenum field is not always valid */     
       line = pline;
  }
  /**
   * matchsymbol       Searches tree for a match to the line.
   * @param  String  pline, a line of text
   * If node's linestate == freshnode, then created the node.
   */
  static node matchsymbol( String pline )
  {
       int comparison;
       node pnode = panchor;
       if ( panchor == null ) return panchor = new node( pline);
       for(;;) {
      comparison = pnode.line.compareTo(pline);
      if ( comparison == 0 ) return pnode;          /* found */
      if ( comparison < 0 ) {
           if ( pnode.pleft == null ) {
          pnode.pleft = new node( pline);
          return pnode.pleft;
           }
           pnode = pnode.pleft;
      }
      if ( comparison > 0 ) {
           if ( pnode.pright == null ) {
          pnode.pright = new node( pline);
          return pnode.pright;
           }
           pnode = pnode.pright;
      }
       }
       /* NOTE: There are return stmts, so control does not get here. */
  }
  /**
   * addSymbol(String pline) - Saves line into the symbol table.
   * Returns a handle to the symtab entry for that unique line.
   * If inoldfile nonzero, then linenum is remembered.
   */
  static node addSymbol( String pline, boolean inoldfile, int linenum )
  {
    node pnode;
    pnode = matchsymbol( pline );  /* find the node in the tree */
    if ( pnode.linestate == freshnode ) {
      pnode.linestate = inoldfile ? oldonce : newonce;
    } else {
      if (( pnode.linestate == oldonce && !inoldfile ) ||
          ( pnode.linestate == newonce &&  inoldfile )) 
           pnode.linestate = bothonce;
      else pnode.linestate = other;
    }
    if (inoldfile) pnode.linenum = linenum;
    return pnode;
  }
  /**
   * symbolIsUnique    Arg is a ptr previously returned by addSymbol.
   * --------------    Returns true if the line was added to the
   *                   symbol table exactly once with inoldfile true,
   *                   and exactly once with inoldfile false.
   */
  boolean symbolIsUnique()
  {
    return (linestate == bothonce );
  }
  /**
   * showSymbol        Prints the line to stdout.
   */
  void showSymbol()
  {
    System.out.println(line);
  }
}