/* JMeld is a visual diff and merge tool. Copyright (C) 2007 Kees Kuip 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jmeld.util.file; import org.jmeld.util.*; import org.jmeld.util.node.*; import java.io.*; import java.nio.*; import java.nio.channels.*; public class CompareUtil { private static final int MAX_LINE_NUMBER = 1000; private static char[] leftLine = new char[MAX_LINE_NUMBER]; private static char[] rightLine = new char[MAX_LINE_NUMBER]; private static CharBuffer leftLineBuffer = CharBuffer.allocate(10000); private static CharBuffer rightLineBuffer = CharBuffer.allocate(10000); private static CharBuffer leftLineOutputBuffer = CharBuffer.allocate(10000); private static CharBuffer rightLineOutputBuffer = CharBuffer.allocate(10000); private CompareUtil() { } public static boolean contentEquals(BufferNode nodeLeft, BufferNode nodeRight, Ignore ignore) { if (nodeLeft instanceof FileNode && nodeRight instanceof FileNode) { return contentEquals((FileNode) nodeLeft, (FileNode) nodeRight, ignore); } else { try { return contentEquals(nodeLeft.getDocument().getReader(), nodeRight .getDocument().getReader(), ignore); } catch (Exception ex) { ex.printStackTrace(); } } return false; } private static boolean contentEquals(FileNode nodeLeft, FileNode nodeRight, Ignore ignore) { File fileLeft; File fileRight; RandomAccessFile fLeft; RandomAccessFile fRight; FileChannel fcLeft; FileChannel fcRight; ByteBuffer bbLeft; ByteBuffer bbRight; boolean equals; fileLeft = nodeLeft.getFile(); fileRight = nodeRight.getFile(); fLeft = null; fRight = null; try { if (fileLeft.isDirectory() || fileRight.isDirectory()) { return true; } if (!ignore.ignore && fileLeft.length() != fileRight.length()) { return false; } // In practice most files that have the same length will // be equal. So eventhough some ignore feature is activated // we will examine if the files are equal. If they are // equal we won't have to execute the expensive // contentEquals method below. This should speed up directory // comparisons quite a bit. if (!ignore.ignore || fileLeft.length() == fileRight.length()) { fLeft = new RandomAccessFile(fileLeft, "r"); fRight = new RandomAccessFile(fileRight, "r"); fcLeft = fLeft.getChannel(); fcRight = fRight.getChannel(); bbLeft = fcLeft.map(FileChannel.MapMode.READ_ONLY, 0, (int) fcLeft .size()); bbRight = fcRight.map(FileChannel.MapMode.READ_ONLY, 0, (int) fcRight .size()); equals = bbLeft.equals(bbRight); if (!ignore.ignore || equals) { return equals; } } equals = contentEquals(nodeLeft.getDocument().getReader(), nodeRight .getDocument().getReader(), ignore); return equals; } catch (Exception ex) { ex.printStackTrace(); return false; } finally { try { if (fLeft != null) { fLeft.close(); } } catch (Exception ex) { ex.printStackTrace(); } try { if (fRight != null) { fRight.close(); } } catch (Exception ex) { ex.printStackTrace(); } } } public static boolean contentEquals(char[] left, char[] right, Ignore ignore) { try { return contentEquals(new CharArrayReader(left), new CharArrayReader(right), ignore); } catch (IOException ex) { // IOException will never happen! return false; } } /** Test if 2 readers are equals (with ignore possibilities). * Synchronized because leftLine and rightLine are static variables for * performance reasons. */ private static synchronized boolean contentEquals(Reader readerLeft, Reader readerRight, Ignore ignore) throws IOException { boolean leftEOF, rightEOF; try { for (;;) { for (;;) { leftEOF = readLine(readerLeft, leftLineBuffer); removeIgnoredChars(leftLineBuffer, ignore, leftLineOutputBuffer); if (leftLineOutputBuffer.remaining() != 0) { break; } if (leftEOF) { break; } } for (;;) { rightEOF = readLine(readerRight, rightLineBuffer); removeIgnoredChars(rightLineBuffer, ignore, rightLineOutputBuffer); if (rightLineOutputBuffer.remaining() != 0) { break; } if (rightEOF) { break; } } if (leftLineOutputBuffer.remaining() != 0 && rightLineOutputBuffer.remaining() != 0) { if (!leftLineOutputBuffer.equals(rightLineOutputBuffer)) { return false; } } if (leftEOF && !rightEOF || !leftEOF && rightEOF) { return false; } if (leftEOF && rightEOF) { return true; } } } finally { readerLeft.close(); readerRight.close(); } } private static boolean readLine(Reader reader, CharBuffer lineBuffer) throws IOException { int c, nextChar; lineBuffer.clear(); while ((c = reader.read()) != -1) { lineBuffer.put((char) c); if (c == '\n') { break; } if (c == '\r') { reader.mark(1); nextChar = reader.read(); if (nextChar == '\n') { lineBuffer.put((char) nextChar); break; } else { reader.reset(); } break; } } if (c == -1) { return true; } return false; } /** Test if 2 readers are equals (with ignore possibilities). * Synchronized because leftLine and rightLine are static variables for * performance reasons. */ private static synchronized boolean contentEquals_old(Reader readerLeft, Reader readerRight, Ignore ignore) throws IOException { boolean equals; boolean leftFound; boolean rightFound; int leftChar; int rightChar; int nextChar; boolean eol; boolean previousEolLeft; boolean previousEolRight; int leftLineIndex; int rightLineIndex; boolean whitespaceAtBegin; int whitespaceIndex; leftLineIndex = 0; rightLineIndex = 0; equals = false; leftChar = 0; rightChar = 0; previousEolLeft = true; previousEolRight = true; try { for (;;) { leftFound = false; whitespaceAtBegin = true; whitespaceIndex = -1; while ((leftChar = readerLeft.read()) != -1) { eol = isEOL(leftChar); if ((ignore.ignoreEOL || ignore.ignoreBlankLines || ignore.ignoreWhitespace) && eol) { readerLeft.mark(1); nextChar = readerLeft.read(); // Try to read the following combinations as 1 newline: // 1. '\n' // 2. '\r' // 3. '\r\n' // 4. '\n\r' if (!((leftChar == '\n' && nextChar == '\r') || (leftChar == '\r' && nextChar == '\n'))) { // The next character doesn't belong to a newline -> unread it! readerLeft.reset(); } // Replace all combinations of a newline with '\n'. The compare is now easy. leftChar = '\n'; } if (ignore.ignoreBlankLines && previousEolLeft && eol) { continue; } if (!eol) { if (ignore.ignoreWhitespace) { if (Character.isWhitespace(leftChar)) { if (whitespaceIndex == -1) { whitespaceIndex = leftLineIndex; } } else { if (whitespaceIndex != -1) { if (whitespaceAtBegin) { whitespaceAtBegin = false; if (ignore.ignoreWhitespaceAtBegin) { leftLineIndex = whitespaceIndex; } } else { if (ignore.ignoreWhitespaceInBetween) { leftLineIndex = whitespaceIndex; } } whitespaceIndex = -1; } } } if (ignore.ignoreCase) { leftChar = Character.toLowerCase(leftChar); } } previousEolLeft = eol; leftLine[leftLineIndex] = (char) leftChar; leftLineIndex++; if (eol || leftLineIndex >= MAX_LINE_NUMBER) { if (whitespaceIndex != -1 && ignore.ignoreWhitespaceAtEnd) { leftLineIndex = whitespaceIndex; } break; } } rightFound = false; whitespaceAtBegin = true; whitespaceIndex = -1; while ((rightChar = readerRight.read()) != -1) { eol = isEOL(rightChar); if ((ignore.ignoreEOL || ignore.ignoreBlankLines || ignore.ignoreWhitespace) && eol) { readerRight.mark(1); nextChar = readerRight.read(); // Try to read the following combinations as 1 newline: // 1. '\n' // 2. '\r' // 3. '\r\n' // 4. '\n\r' if (!((rightChar == '\n' && nextChar == '\r') || (rightChar == '\r' && nextChar == '\n'))) { // The next character doesn't belong to a newline -> unread it! readerRight.reset(); } rightChar = '\n'; } if (ignore.ignoreBlankLines && previousEolRight && eol) { continue; } if (!eol) { if (ignore.ignoreWhitespace) { if (Character.isWhitespace(rightChar)) { if (whitespaceIndex == -1) { whitespaceIndex = rightLineIndex; } } else { if (whitespaceIndex != -1) { if (whitespaceAtBegin) { whitespaceAtBegin = false; if (ignore.ignoreWhitespaceAtBegin) { rightLineIndex = whitespaceIndex; } } else { if (ignore.ignoreWhitespaceInBetween) { rightLineIndex = whitespaceIndex; } } whitespaceIndex = -1; } } } if (ignore.ignoreCase) { rightChar = Character.toLowerCase(rightChar); } } previousEolRight = eol; rightLine[rightLineIndex] = (char) rightChar; rightLineIndex++; if (eol || rightLineIndex >= MAX_LINE_NUMBER) { if (whitespaceIndex != -1 && ignore.ignoreWhitespaceAtEnd) { rightLineIndex = whitespaceIndex; } break; } } if (leftLineIndex > 0 && leftLineIndex == rightLineIndex) { if (equals(leftLine, rightLine, leftLineIndex)) { leftLineIndex = 0; rightLineIndex = 0; continue; } equals = false; break; } if (leftLineIndex != rightLineIndex) { equals = false; break; } if (leftLineIndex == 0 && rightLineIndex == 0) { equals = true; break; } } } finally { readerLeft.close(); readerRight.close(); } return equals; } private static boolean equals(char[] a1, char[] a2, int size) { for (int i = 0; i < size; i++) { if (a1[i] != a2[i]) { return false; } } return true; } public static boolean isEOL(int character) { return character == '\n' || character == '\r'; } /** Remove all characters from the 'line' that can be ignored. * @param inputLine char[] representing a line. * @param ignore an object with the ignore options. * @param outputLine return value which contains all characters from line that cannot be * ignored. It is a parameter that can be reused (which is important for * performance) */ public static void removeIgnoredChars(CharBuffer inputLine, Ignore ignore, CharBuffer outputLine) { boolean whitespaceAtBegin; boolean blankLine; int lineEndingEndIndex; int whitespaceEndIndex; int length; char c; boolean whiteSpaceInBetweenIgnored; inputLine.flip(); outputLine.clear(); length = inputLine.remaining(); lineEndingEndIndex = length; blankLine = true; whiteSpaceInBetweenIgnored = false; c = 0; for (int index = lineEndingEndIndex - 1; index >= 0; index--) { if (!isEOL(inputLine.charAt(index))) { break; } lineEndingEndIndex--; } whitespaceEndIndex = lineEndingEndIndex; for (int index = whitespaceEndIndex - 1; index >= 0; index--) { if (!Character.isWhitespace(inputLine.charAt(index))) { break; } whitespaceEndIndex--; } whitespaceAtBegin = true; for (int i = 0; i < length; i++) { c = inputLine.get(i); if (i < whitespaceEndIndex) { if (Character.isWhitespace(c)) { if (whitespaceAtBegin) { if (ignore.ignoreWhitespaceAtBegin) { continue; } } else { if (ignore.ignoreWhitespaceInBetween) { whiteSpaceInBetweenIgnored = true; continue; } } } whitespaceAtBegin = false; blankLine = false; // The character won't be ignored! } else if (i < lineEndingEndIndex) { if (ignore.ignoreWhitespaceAtEnd) { continue; } blankLine = false; // The character won't be ignored! } else { if (ignore.ignoreEOL) { continue; } // The character won't be ignored! } if (ignore.ignoreCase) { c = Character.toLowerCase(c); } if(whiteSpaceInBetweenIgnored) { //outputLine.put(' '); whiteSpaceInBetweenIgnored = false; } outputLine.put(c); } if (outputLine.position() == 0 && !ignore.ignoreBlankLines) { outputLine.put('\n'); } if (blankLine && ignore.ignoreBlankLines) { outputLine.clear(); } outputLine.flip(); } }