Java tutorial
/** * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.util.diff; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.incava.util.diff.Diff; import org.incava.util.diff.Difference; /** * <p> * This class can compare two different versions of a text. Source refers to the * earliest version of the text and target refers to a modified version of * source. Changes are considered either as a removal from the source or as an * addition to the target. This class detects changes to an entire line and also * detects changes within lines, such as, removal or addition of characters. * Take a look at <code>DiffTest</code> to see the expected inputs and outputs. * </p> * * @author Bruno Farache * @deprecated Moved to {@link com.liferay.portal.kernel.util.DiffUtil} */ public class DiffUtil { public static final String OPEN_INS = "<ins>"; public static final String CLOSE_INS = "</ins>"; public static final String OPEN_DEL = "<del>"; public static final String CLOSE_DEL = "</del>"; public static final String CONTEXT_LINE = "#context#line#"; /** * This is a diff method with default values. * * @return an array containing two lists of <code>DiffResults</code>, the * first element contains DiffResults related to changes in source * and the second element to changes in target */ public static List<DiffResult>[] diff(Reader source, Reader target) { int margin = 2; return diff(source, target, OPEN_INS, CLOSE_INS, OPEN_DEL, CLOSE_DEL, margin); } /** * The main entrance of this class. This method will compare the two texts, * highlight the changes by enclosing them with markers and return a list of * <code>DiffResults</code>. * * @return an array containing two lists of <code>DiffResults</code>, the * first element contains DiffResults related to changes in source * and the second element to changes in target */ public static List<DiffResult>[] diff(Reader source, Reader target, String addedMarkerStart, String addedMarkerEnd, String deletedMarkerStart, String deletedMarkerEnd, int margin) { List<DiffResult> sourceResults = new ArrayList<DiffResult>(); List<DiffResult> targetResults = new ArrayList<DiffResult>(); List<DiffResult>[] results = new List[] { sourceResults, targetResults }; // Convert the texts to Lists where each element are lines of the texts. List<String> sourceStringList = FileUtil.toList(source); List<String> targetStringList = FileUtil.toList(target); // Make a a Diff of these lines and iterate over their Differences. Diff diff = new Diff(sourceStringList, targetStringList); List<Difference> differences = diff.diff(); Iterator<Difference> itr = differences.iterator(); while (itr.hasNext()) { Difference difference = itr.next(); if (difference.getAddedEnd() == Difference.NONE) { // Lines were deleted from source only. _highlightLines(sourceStringList, deletedMarkerStart, deletedMarkerEnd, difference.getDeletedStart(), difference.getDeletedEnd()); margin = _calculateMargin(sourceResults, targetResults, difference.getDeletedStart(), difference.getAddedStart(), margin); List<String> changedLines = _addMargins(sourceResults, sourceStringList, difference.getDeletedStart(), margin); _addResults(sourceResults, sourceStringList, changedLines, difference.getDeletedStart(), difference.getDeletedEnd()); changedLines = _addMargins(targetResults, targetStringList, difference.getAddedStart(), margin); int deletedLines = difference.getDeletedEnd() + 1 - difference.getDeletedStart(); for (int i = 0; i < deletedLines; i++) { changedLines.add(CONTEXT_LINE); } DiffResult diffResult = new DiffResult(difference.getDeletedStart(), changedLines); targetResults.add(diffResult); } else if (difference.getDeletedEnd() == Difference.NONE) { // Lines were added to target only. _highlightLines(targetStringList, addedMarkerStart, addedMarkerEnd, difference.getAddedStart(), difference.getAddedEnd()); margin = _calculateMargin(sourceResults, targetResults, difference.getDeletedStart(), difference.getAddedStart(), margin); List<String> changedLines = _addMargins(sourceResults, sourceStringList, difference.getDeletedStart(), margin); int addedLines = difference.getAddedEnd() + 1 - difference.getAddedStart(); for (int i = 0; i < addedLines; i++) { changedLines.add(CONTEXT_LINE); } DiffResult diffResult = new DiffResult(difference.getAddedStart(), changedLines); sourceResults.add(diffResult); changedLines = _addMargins(targetResults, targetStringList, difference.getAddedStart(), margin); _addResults(targetResults, targetStringList, changedLines, difference.getAddedStart(), difference.getAddedEnd()); } else { // Lines were deleted from source and added to target at the // same position. It needs to check for characters differences. _checkCharDiffs(sourceResults, targetResults, sourceStringList, targetStringList, addedMarkerStart, addedMarkerEnd, deletedMarkerStart, deletedMarkerEnd, difference, margin); } } return results; } private static List<String> _addMargins(List<DiffResult> results, List<String> stringList, int startPos, int margin) { List<String> changedLines = new ArrayList<String>(); if (margin == 0 || startPos == 0) { return changedLines; } int i = startPos - margin; for (; i < 0; i++) { changedLines.add(CONTEXT_LINE); } for (; i < startPos; i++) { if (i < stringList.size()) { changedLines.add(stringList.get(i)); } } return changedLines; } private static void _addResults(List<DiffResult> results, List<String> stringList, List<String> changedLines, int start, int end) { changedLines.addAll(stringList.subList(start, end + 1)); DiffResult diffResult = new DiffResult(start, changedLines); results.add(diffResult); } private static int _calculateMargin(List<DiffResult> sourceResults, List<DiffResult> targetResults, int sourceBeginPos, int targetBeginPos, int margin) { int sourceMargin = _checkOverlapping(sourceResults, sourceBeginPos, margin); int targetMargin = _checkOverlapping(targetResults, targetBeginPos, margin); if (sourceMargin < targetMargin) { return sourceMargin; } return targetMargin; } private static void _checkCharDiffs(List<DiffResult> sourceResults, List<DiffResult> targetResults, List<String> sourceStringList, List<String> targetStringList, String addedMarkerStart, String addedMarkerEnd, String deletedMarkerStart, String deletedMarkerEnd, Difference difference, int margin) { boolean aligned = false; int i = difference.getDeletedStart(); int j = difference.getAddedStart(); // A line with changed characters may have its position shifted some // lines above or below. These for loops will try to align these lines. // While these lines are not aligned, highlight them as either additions // or deletions. for (; i <= difference.getDeletedEnd(); i++) { for (; j <= difference.getAddedEnd(); j++) { if (_lineDiff(sourceResults, targetResults, sourceStringList, targetStringList, addedMarkerStart, addedMarkerEnd, deletedMarkerStart, deletedMarkerEnd, i, j, false)) { aligned = true; break; } else { _highlightLines(targetStringList, addedMarkerStart, addedMarkerEnd, j, j); DiffResult targetResult = new DiffResult(j, targetStringList.subList(j, j + 1)); targetResults.add(targetResult); sourceResults.add(new DiffResult(j, CONTEXT_LINE)); } } if (aligned) { break; } else { _highlightLines(sourceStringList, deletedMarkerStart, deletedMarkerEnd, i, i); DiffResult sourceResult = new DiffResult(i, sourceStringList.subList(i, i + 1)); sourceResults.add(sourceResult); targetResults.add(new DiffResult(i, CONTEXT_LINE)); } } i = i + 1; j = j + 1; // Lines are aligned, check for differences of the following lines. for (; i <= difference.getDeletedEnd() && j <= difference.getAddedEnd(); i++, j++) { _lineDiff(sourceResults, targetResults, sourceStringList, targetStringList, addedMarkerStart, addedMarkerEnd, deletedMarkerStart, deletedMarkerEnd, i, j, true); } // After the for loop above, some lines might remained unchecked. // They are considered as deletions or additions. for (; i <= difference.getDeletedEnd(); i++) { _highlightLines(sourceStringList, deletedMarkerStart, deletedMarkerEnd, i, i); DiffResult sourceResult = new DiffResult(i, sourceStringList.subList(i, i + 1)); sourceResults.add(sourceResult); targetResults.add(new DiffResult(i, CONTEXT_LINE)); } for (; j <= difference.getAddedEnd(); j++) { _highlightLines(targetStringList, addedMarkerStart, addedMarkerEnd, j, j); DiffResult targetResult = new DiffResult(j, targetStringList.subList(j, j + 1)); targetResults.add(targetResult); sourceResults.add(new DiffResult(j, CONTEXT_LINE)); } } private static int _checkOverlapping(List<DiffResult> results, int startPos, int margin) { if (results.size() == 0 || (startPos - margin) < 0) { return margin; } DiffResult lastDiff = results.get(results.size() - 1); if (lastDiff.getChangedLines().size() == 0) { return margin; } int lastChangedLine = (lastDiff.getLineNumber() - 1) + lastDiff.getChangedLines().size(); int currentChangedLine = startPos - margin; if ((lastDiff.getChangedLines().size() == 1) && (lastDiff.getChangedLines().get(0).equals(CONTEXT_LINE))) { currentChangedLine = currentChangedLine + 1; } if (currentChangedLine < lastChangedLine) { return margin + currentChangedLine - lastChangedLine; } return margin; } private static void _highlightChars(List<String> stringList, String markerStart, String markerEnd, int startPos, int endPos) { String start = markerStart + stringList.get(startPos); stringList.set(startPos, start); String end = stringList.get(endPos) + markerEnd; stringList.set(endPos, end); } private static void _highlightLines(List<String> stringList, String markerStart, String markerEnd, int startPos, int endPos) { for (int i = startPos; i <= endPos; i++) { stringList.set(i, markerStart + stringList.get(i) + markerEnd); } } private static boolean _lineDiff(List<DiffResult> sourceResults, List<DiffResult> targetResults, List<String> sourceStringList, List<String> targetStringList, String addedMarkerStart, String addedMarkerEnd, String deletedMarkerStart, String deletedMarkerEnd, int sourceChangedLine, int targetChangedLine, boolean aligned) { String source = sourceStringList.get(sourceChangedLine); String target = targetStringList.get(targetChangedLine); // Convert the lines to lists where each element are chars of the lines. List<String> sourceList = _toList(source); List<String> targetList = _toList(target); Diff diff = new Diff(sourceList, targetList); List<Difference> differences = diff.diff(); Iterator<Difference> itr = differences.iterator(); int deletedChars = 0; int addedChars = 0; // The following while loop will calculate how many characters of // the source line need to be changed to be equals to the target line. while (itr.hasNext() && !aligned) { Difference difference = itr.next(); if (difference.getDeletedEnd() != Difference.NONE) { deletedChars = deletedChars + (difference.getDeletedEnd() - difference.getDeletedStart() + 1); } if (difference.getAddedEnd() != Difference.NONE) { addedChars = addedChars + (difference.getAddedEnd() - difference.getAddedStart() + 1); } } // If a lot of changes were needed (more than half of the source line // length), consider this as not aligned yet. if ((deletedChars > (sourceList.size() / 2)) || (addedChars > sourceList.size() / 2)) { return false; } itr = differences.iterator(); boolean sourceChanged = false; boolean targetChanged = false; // Iterate over Differences between chars of these lines. while (itr.hasNext()) { Difference difference = itr.next(); if (difference.getAddedEnd() == Difference.NONE) { // Chars were deleted from source only. _highlightChars(sourceList, deletedMarkerStart, deletedMarkerEnd, difference.getDeletedStart(), difference.getDeletedEnd()); sourceChanged = true; } else if (difference.getDeletedEnd() == Difference.NONE) { // Chars were added to target only. _highlightChars(targetList, addedMarkerStart, addedMarkerEnd, difference.getAddedStart(), difference.getAddedEnd()); targetChanged = true; } else { // Chars were both deleted and added. _highlightChars(sourceList, deletedMarkerStart, deletedMarkerEnd, difference.getDeletedStart(), difference.getDeletedEnd()); sourceChanged = true; _highlightChars(targetList, addedMarkerStart, addedMarkerEnd, difference.getAddedStart(), difference.getAddedEnd()); targetChanged = true; } } if (sourceChanged) { DiffResult sourceResult = new DiffResult(sourceChangedLine, _toString(sourceList)); sourceResults.add(sourceResult); if (!targetChanged) { DiffResult targetResult = new DiffResult(targetChangedLine, target); targetResults.add(targetResult); } } if (targetChanged) { if (!sourceChanged) { DiffResult sourceResult = new DiffResult(sourceChangedLine, source); sourceResults.add(sourceResult); } DiffResult targetResult = new DiffResult(targetChangedLine, _toString(targetList)); targetResults.add(targetResult); } return true; } private static List<String> _toList(String line) { String[] stringArray = line.split(StringPool.BLANK); List<String> result = new ArrayList<String>(); for (int i = 1; i < stringArray.length; i++) { result.add(stringArray[i]); } return result; } private static String _toString(List<String> line) { if (line.isEmpty()) { return StringPool.BLANK; } StringBundler sb = new StringBundler(line.size()); Iterator<String> itr = line.iterator(); while (itr.hasNext()) { sb.append(itr.next()); } return sb.toString(); } }