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.6 2006/08/05 11:18:50 chalin 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.6 $
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 public Diff(/*@ non_null */ String oldTextLabel,
049 /*@ non_null */ String oldText,
050 /*@ non_null */ String newTextLabel,
051 /*@ non_null */ String newText)
052 {
053 this.oldText = oldText;
054 this.newText = newText;
055 calculate(oldTextLabel, newTextLabel);
056 }
057
058 //---------------------------------------------------------------------
059 // OPERATIONS
060 //---------------------------------------------------------------------
061
062 /**
063 * Sets the values of _areDifferent and differences according to a comparison
064 * between oldText and newText
065 *
066 * @param oldTextLabel a label for the <code>oldText</code> parameter
067 * @param newTextLabel a label for the <code>newText</code> parameter
068 */
069 //@ ensures _areDifferent == areDifferent;
070 private /*@ helper */ void calculate(/*@ non_null */ String oldTextLabel,
071 /*@ non_null */ String newTextLabel) {
072 // Accumulate the diff in differencesSB
073 StringBuffer differencesSB = new StringBuffer(newText.length());
074
075 calculateDiffs(differencesSB);
076 if (differencesSB.length() > 0) {
077 // Some diffs accumulated, so the strings are different
078 _areDifferent = true;
079
080 // Prepend a key to the results.
081 differences = NEWLINE + OLD_CH + oldTextLabel + NEWLINE + NEW_CH
082 + newTextLabel + NEWLINE + NEWLINE + differencesSB.toString();
083 } else {
084 // No diffs accumulated, so the strings must be the same
085 _areDifferent = false;
086 differences = "";
087 }
088 }
089
090 private /*@ helper */ void calculateDiffs(/*@ non_null */ StringBuffer differencesSB) {
091 String[] oldTextLines = splitByLine(oldText);
092 String[] newTextLines = splitByLine(newText);
093
094 // match by lines
095 int oPos = 0;
096 int nPos = 0;
097 int lastOldMatch = -1;
098 int lastNewMatch = -1;
099 if (oldTextLines.length > 0 && newTextLines.length > 0)
100 while (oPos < oldTextLines.length || nPos < newTextLines.length) {
101 // this is a pretty dumb algorithm that doesn't handle
102 // things like the insertion of a single new line in one
103 // string or grouping
104 if (oPos >= oldTextLines.length) oPos = oldTextLines.length-1;
105 if (nPos >= newTextLines.length) nPos = newTextLines.length-1;
106 boolean matched = false;
107 for (int i = lastOldMatch+1; i<=oPos; ++i) {
108 if (oldTextLines[i].equals(newTextLines[nPos])) {
109 // Got a match
110 for (int j=lastOldMatch+1; j<i; ++j)
111 differencesSB.append((j+1) + OLD_CH + oldTextLines[j] + NEWLINE);
112 for (int j=lastNewMatch+1; j<nPos; ++j)
113 differencesSB.append((j+1) + NEW_CH + newTextLines[j] + NEWLINE);
114 lastOldMatch = i;
115 lastNewMatch = nPos;
116 oPos = i+1;
117 nPos++;
118 matched = true;
119 break;
120 }
121 }
122 if (matched) continue;
123 for (int i = lastNewMatch+1; i<=nPos; ++i) {
124 if (newTextLines[i].equals(oldTextLines[oPos])) {
125 // Got a match
126 for (int j=lastOldMatch+1; j<oPos; ++j)
127 differencesSB.append((j+1) + OLD_CH + oldTextLines[j] + NEWLINE);
128 for (int j=lastNewMatch+1; j<i; ++j)
129 differencesSB.append((j+1) + NEW_CH + newTextLines[j] + NEWLINE);
130 lastOldMatch = oPos;
131 lastNewMatch = i;
132 oPos++;
133 nPos = i + 1;
134 matched = true;
135 break; }
136 }
137 if (matched) continue;
138 oPos++;
139 nPos++;
140 }
141 // If we reached the end of one array before the other, then this
142 // will print the remainders.
143 for (oPos=lastOldMatch+1; oPos < oldTextLines.length; oPos++) {
144 differencesSB.append((oPos + 1) + OLD_CH + oldTextLines[oPos] + NEWLINE);
145 } // end of for ()
146 for (nPos=lastNewMatch+1; nPos < newTextLines.length; nPos++) {
147 differencesSB.append((nPos + 1) + NEW_CH + newTextLines[nPos] + NEWLINE);
148 } // end of for ()
149 }
150
151 //@ ensures \nonnullelements(\result);
152 private /*@ helper non_null */ String[] splitByLine(/*@ non_null */ 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 /*@ public normal_behavior
170 @ ensures \result == areDifferent;
171 @*/
172 public /*@ pure @*/ boolean areDifferent() {
173 return _areDifferent;
174 }
175
176 /**
177 * Returns the differences between the given strings.
178 *
179 */
180 //@ private normal_behavior
181 //@ ensures \result == differences;
182 //@ pure
183 public String result() {
184 return differences;
185 }
186
187 //---------------------------------------------------------------------
188 // PRIVILEGED DATA
189 //---------------------------------------------------------------------
190
191 /** This is the supplied old text, to be compared against the new text */
192 //@ non_null
193 private String oldText;
194
195 /** This is the supplied new text, to be compared against the old text */
196 //@ non_null
197 private String newText;
198
199 /** This is set to true if the oldText and newText are not the same */
200 private boolean _areDifferent;
201
202 /**
203 * This output String holds the description of the differences between the old
204 * and new text.
205 */
206 /*@ spec_public */ private /*@ non_null */ String differences;
207
208 //@ public model boolean areDifferent;
209 //@ public represents areDifferent <- !differences.equals("");
210
211 //@ private invariant _areDifferent == areDifferent;
212
213 /** This string holds line delimiters */
214 //@ non_null
215 private static final String DELIM = "\n\r\f";
216
217 /** This is the system new line character */
218 //@ non_null
219 private static final String NEWLINE = System.getProperty("line.separator");
220
221 /** This string is used to mark lines of old text */
222 //@ non_null
223 private static final String OLD_CH = "< ";
224
225 /** This string is used to mark lines of new text */
226 //@ non_null
227 private static final String NEW_CH = "> ";
228 }