Mega Code Archive

 
Categories / Java / File Input Output
 

Diff

/*  * 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);   } }