/** * Find the differences between two texts. Simplifies the problem by stripping any common prefix * or suffix off the texts before diffing. * * @return List of Difference objects */ public List<Difference> compare() { // Check for equality (speedup) List<Difference> diffs; if (source.equals(target)) { diffs = new ArrayList<Difference>(); diffs.add(new Difference(EditType.EQUAL, source)); return diffs; } // Trim off common prefix (speedup) int commonLength = Commonality.prefix(source, target); String commonPrefix = source.substring(0, commonLength); source = source.substring(commonLength); target = target.substring(commonLength); // Trim off common suffix (speedup) commonLength = Commonality.suffix(source, target); String commonSuffix = source.substring(source.length() - commonLength); source = source.substring(0, source.length() - commonLength); target = target.substring(0, target.length() - commonLength); // Compute the diff on the middle block diffs = compute(); // Restore the prefix and suffix if (!"".equals(commonPrefix)) { diffs.add(0, new Difference(EditType.EQUAL, commonPrefix)); } if (!"".equals(commonSuffix)) { diffs.add(new Difference(EditType.EQUAL, commonSuffix)); } DiffCleanup.cleanupMerge(diffs); return diffs; }
/** * Find the differences between two texts. * * @return List of Difference objects */ private List<Difference> compute() { List<Difference> diffs = new ArrayList<Difference>(); if ("".equals(source)) { // Just add some text (speedup) diffs.add(new Difference(EditType.INSERT, target)); return diffs; } if ("".equals(target)) { // Just delete some text (speedup) diffs.add(new Difference(EditType.DELETE, source)); return diffs; } String longText = source.length() > target.length() ? source : target; String shortText = source.length() > target.length() ? target : source; int i = longText.indexOf(shortText); if (i != -1) { // Shorter text is inside the longer text (speedup) EditType editType = (source.length() > target.length()) ? EditType.DELETE : EditType.INSERT; diffs.add(new Difference(editType, longText.substring(0, i))); diffs.add(new Difference(EditType.EQUAL, shortText)); diffs.add(new Difference(editType, longText.substring(i + shortText.length()))); return diffs; } // Check to see if the problem can be split in two. CommonMiddle middleMatch = Commonality.halfMatch(source, target); if (middleMatch != null) { // A half-match was found, sort out the return data. // Send both pairs off for separate processing. Diff startDiff = new Diff(middleMatch.getSourcePrefix(), middleMatch.getTargetPrefix(), checkLines); Diff endDiff = new Diff(middleMatch.getSourceSuffix(), middleMatch.getTargetSuffix(), checkLines); // Merge the results. diffs = startDiff.compare(); diffs.add(new Difference(EditType.EQUAL, middleMatch.getCommonality())); diffs.addAll(endDiff.compare()); return diffs; } // Perform a real diff. if (checkLines && source.length() + target.length() < 250) { checkLines = false; // Too trivial for the overhead. } LineMap lineMap = null; if (checkLines) { // Scan the text on a line-by-line basis first. lineMap = new LineMap(source, target); source = lineMap.getSourceMap(); target = lineMap.getTargetMap(); } diffs = new DifferenceEngine(source, target).generate(); if (diffs == null) { // No acceptable result. diffs = new ArrayList<Difference>(); diffs.add(new Difference(EditType.DELETE, source)); diffs.add(new Difference(EditType.INSERT, target)); } if (checkLines && lineMap != null) { // Convert the diff back to original text. lineMap.restore(diffs); // Eliminate freak matches (e.g. blank lines) DiffCleanup.cleanupSemantic(diffs); // Rediff any replacement blocks, this time character-by-character. // Add a dummy entry at the end. diffs.add(new Difference(EditType.EQUAL, "")); int countDeletes = 0; int countInserts = 0; StringBuilder textDelete = new StringBuilder(); StringBuilder textInsert = new StringBuilder(); ListIterator<Difference> pointer = diffs.listIterator(); Difference curDiff = pointer.next(); while (curDiff != null) { EditType editType = curDiff.getEditType(); if (EditType.INSERT.equals(editType)) { countInserts++; textInsert.append(curDiff.getText()); } else if (EditType.DELETE.equals(editType)) { countDeletes++; textDelete.append(curDiff.getText()); } else { // Upon reaching an equality, check for prior redundancies. if (countDeletes >= 1 && countInserts >= 1) { // Delete the offending records and add the merged ones. pointer.previous(); for (int j = 0; j < countDeletes + countInserts; j++) { pointer.previous(); pointer.remove(); } Diff newDiff = new Diff(textDelete.toString(), textInsert.toString(), false); for (Difference diff : newDiff.compare()) { pointer.add(diff); } } countInserts = 0; countDeletes = 0; textDelete.delete(0, textDelete.length()); textInsert.delete(0, textInsert.length()); } curDiff = pointer.hasNext() ? pointer.next() : null; } diffs.remove(diffs.size() - 1); // Remove the dummy entry at the // end. } return diffs; }