    001    /*
    002     * Copyright (C) 2000-2001 Iowa State University
    003     *
    004     * This file is part of mjc, the MultiJava Compiler, and the JML Project.
    005     *
    006     * This program is free software; you can redistribute it and/or modify it under
    007     * the terms of the GNU General Public License as published by the Free Software
    008     * Foundation; either version 2 of the License, or (at your option) any later
    009     * version.
    010     *
    011     * This program is distributed in the hope that it will be useful, but WITHOUT
    012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    013     * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
    014     * details.
    015     *
    016     * You should have received a copy of the GNU General Public License along with
    017     * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
    018     * Place, Suite 330, Boston, MA 02111-1307 USA
    019     *
    020     * $Id: Diff.java,v 1.4 2005/08/21 20:35:42 jkiniry Exp $
    021     */
    022
    023    package junitutils;
    024
    025    import java.util.StringTokenizer;
    026    import java.util.ArrayList;
    027
    028    /**
    029     * Class for calculating a (somewhat) detailed comparison of two strings.
    030     *
    031     * @author Curtis Clifton
    032     * @version $Revision: 1.4 $
    033     */
    034
    035    public class Diff {
    036
    037      //---------------------------------------------------------------------
    038      // CONSTRUCTORS
    039      //---------------------------------------------------------------------
    040      /**
    041       * Calculate a difference between the given strings.
    042       *
    043       * @param oldTextLabel a label for the <code>oldText</code> parameter
    044       * @param oldText a value to be compared
    045       * @param newTextLabel a label for the <code>newText</code> parameter
    046       * @param newText a value to be compared
    047       */
    048      //@ requires oldTextLabel != null;
    049      //@ requires oldText != null;
    050      //@ requires newTextLabel != null;
    051      //@ requires newText != null;
    052      public Diff(String oldTextLabel, String oldText, String newTextLabel,
    053          String newText) {
    054        this.oldText = oldText;
    055        this.newText = newText;
    056        calculate(oldTextLabel, newTextLabel);
    057      }
    058
    059      //---------------------------------------------------------------------
    060      // OPERATIONS
    061      //---------------------------------------------------------------------
    062
    063      /**
    064       * Sets the values of areDifferent and result according to a comparison
    065       * between oldText and newText
    066       *
    067       * @param oldTextLabel a label for the <code>oldText</code> parameter
    068       * @param newTextLabel a label for the <code>newText</code> parameter
    069       */
    070      //@ requires oldTextLabel != null;
    071      //@ requires newTextLabel != null;
    072      private void calculate(String oldTextLabel, String newTextLabel) {
    073        // Accumulate the diff in resultSB
    074        StringBuffer resultSB = new StringBuffer(newText.length());
    075
    076        String[] oldTextLines = splitByLine(oldText);
    077        String[] newTextLines = splitByLine(newText);
    078
    079        // match by lines
    080        int oPos = 0;
    081        int nPos = 0;
    082        int lastOldMatch = -1;
    083        int lastNewMatch = -1;
    084        if (oldTextLines.length > 0 && newTextLines.length > 0)
    085         while (oPos < oldTextLines.length || nPos < newTextLines.length) {
    086          // this is a pretty dumb algorithm that doesn't handle
    087          // things like the insertion of a single new line in one
    088          // string or grouping
    089          if (oPos >= oldTextLines.length) oPos = oldTextLines.length-1;
    090          if (nPos >= newTextLines.length) nPos = newTextLines.length-1;
    091          boolean matched = false;
    092          for (int i = lastOldMatch+1; i<=oPos; ++i) {
    093            if (oldTextLines[i].equals(newTextLines[nPos])) {
    094              // Got a match
    095              for (int j=lastOldMatch+1; j<i; ++j)
    096                resultSB.append((j+1) + OLD_CH + oldTextLines[j] + NEWLINE);
    097              for (int j=lastNewMatch+1; j<nPos; ++j)
    098                resultSB.append((j+1) + NEW_CH + newTextLines[j] + NEWLINE);
    099              lastOldMatch = i;
    100              lastNewMatch = nPos;
    101              oPos = i+1;
    102              nPos++;
    103              matched = true;
    104              break;
    105            }
    106          }
    107          if (matched) continue;
    108          for (int i = lastNewMatch+1; i<=nPos; ++i) {
    109            if (newTextLines[i].equals(oldTextLines[oPos])) {
    110              // Got a match
    111              for (int j=lastOldMatch+1; j<oPos; ++j)
    112                resultSB.append((j+1) + OLD_CH + oldTextLines[j] + NEWLINE);
    113              for (int j=lastNewMatch+1; j<i; ++j)
    114                resultSB.append((j+1) + NEW_CH + newTextLines[j] + NEWLINE);
    115              lastOldMatch = oPos;
    116              lastNewMatch = i;
    117              oPos++;
    118              nPos = i + 1;
    119              matched = true;
    120              break;        }
    121          }
    122          if (matched) continue;
    123          oPos++;
    124          nPos++;
    125        }
    126        // If we reached the end of one array before the other, then this
    127        // will print the remainders.
    128        for (oPos=lastOldMatch+1; oPos < oldTextLines.length; oPos++) {
    129          resultSB.append((oPos + 1) + OLD_CH + oldTextLines[oPos] + NEWLINE);
    130        } // end of for ()
    131        for (nPos=lastNewMatch+1; nPos < newTextLines.length; nPos++) {
    132          resultSB.append((nPos + 1) + NEW_CH + newTextLines[nPos] + NEWLINE);
    133        } // end of for ()
    134
    135        if (resultSB.length() > 0) {
    136          // Some diffs accumulated, so the strings are different
    137          areDifferent = true;
    138
    139          // Prepend a key to the results.
    140          result = NEWLINE + OLD_CH + oldTextLabel + NEWLINE + NEW_CH
    141              + newTextLabel + NEWLINE + NEWLINE + resultSB.toString();
    142        } else {
    143          // No diffs accumulated, so the strings must be the same
    144          areDifferent = false;
    145          result = "";
    146        }
    147      }
    148
    149      //@ requires text != null;
    150      //@ ensures \result != null;
    151      //@ ensures \nonnullelements(\result);
    152      private String[] splitByLine(String text) {
    153        // thanks to Windows ridiculous two character newlines it is
    154        // hard to detect blank lines, so we don't bother trying
    155        StringTokenizer toker = new StringTokenizer(text, DELIM, false);
    156        ArrayList lines = new ArrayList(oldText.length() / 60);
    157        while (toker.hasMoreTokens()) {
    158          String tok = toker.nextToken();
    159          lines.add(tok);
    160        }
    161        String[] result = new String[lines.size()];
    162        lines.toArray(result);
    163        return result;
    164      }
    165
    166      /**
    167       * Returns true if strings on which this was constructed are different.
    168       */
    169      //@ private behavior
    170      //@ ensures \result == areDifferent;
    171      public /*@ pure @*/ boolean areDifferent() {
    172        return areDifferent;
    173      }
    174
    175      /**
    176       * Returns the differences between the given strings.
    177       *
    178       */
    179      //@ private normal_behavior
    180      //@ ensures \result == result;
    181      //@ pure
    182      public String result() {
    183        return result;
    184      }
    185
    186      //---------------------------------------------------------------------
    187      // PRIVILEGED DATA
    188      //---------------------------------------------------------------------
    189
    190      /** This is the supplied old text, to be compared against the new text */
    191      //@ non_null
    192      private String oldText;
    193
    194      /** This is the supplied new text, to be compared against the old text */
    195      //@ non_null
    196      private String newText;
    197
    198      /** This is set to true if the oldText and newText are not the same */
    199      private boolean areDifferent = false;
    200
    201      /**
    202       * This output String holds the description of the differences between the old
    203       * and new text.
    204       */
    205      //@ non_null
    206      private String result = "";
    207
    208      //@ private invariant areDifferent <=!=> result.equals("");
    209
    210      /** This string holds line delimiters */
    211      //@ non_null
    212      private static final String DELIM = "\n\r\f";
    213
    214      /** This is the system new line character */
    215      //@ non_null
    216      private static final String NEWLINE = System.getProperty("line.separator");
    217
    218      /** This string is used to mark lines of old text */
    219      //@ non_null
    220      private static final String OLD_CH = "< ";
    221
    222      /** This string is used to mark lines of new text */
    223      //@ non_null
    224      private static final String NEW_CH = "> ";
    225    }