/* 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.diff; import org.jmeld.util.*; import java.util.*; public class JMRevision { // Class variables: private static boolean incrementalUpdateActivated = false; private static boolean debug = false; // Instance variables: private Object[] orgArray; private Object[] revArray; private LinkedList<JMDelta> deltaList; private Ignore ignore; public JMRevision(Object[] orgArray, Object[] revArray) { this.orgArray = orgArray; this.revArray = revArray; deltaList = new LinkedList<JMDelta>(); ignore = Ignore.NULL_IGNORE; } public void setIgnore(Ignore ignore) { this.ignore = ignore; } public void add(JMDelta delta) { deltaList.add(delta); delta.setRevision(this); } public List<JMDelta> getDeltas() { return deltaList; } public void update(Object[] oArray, Object[] rArray) { this.orgArray = oArray; this.revArray = rArray; } /** The arrays have changed! Try to change the delta's incrementally. * This solves a performance issue while editing one of the array's. */ public boolean update(Object[] oArray, Object[] rArray, boolean original, int startLine, int numberOfLines) { update(oArray, rArray); return incrementalUpdate(original, startLine, numberOfLines); } private boolean incrementalUpdate(boolean original, int startLine, int numberOfLines) { JMChunk chunk; List<JMDelta> deltaListToRemove; List<JMChunk> chunkListToChange; int endLine; int orgStartLine; int orgEndLine; int revStartLine; int revEndLine; JMRevision deltaRevision; int index; Object[] orgArrayDelta; Object[] revArrayDelta; JMDelta firstDelta; int length; // It is not yet production ready ! if (!incrementalUpdateActivated) { return false; } System.out.println((original ? "left" : "right") + " changed starting at line " + startLine + " #" + numberOfLines); if (original) { orgStartLine = startLine; orgEndLine = startLine + (numberOfLines < 0 ? 0 : numberOfLines) + 1; revStartLine = DiffUtil.getRevisedLine(this, startLine); revEndLine = DiffUtil.getRevisedLine(this, startLine + (numberOfLines > 0 ? 0 : -numberOfLines)) + 1; } else { revStartLine = startLine; revEndLine = startLine + (numberOfLines < 0 ? 0 : numberOfLines) + 1; orgStartLine = DiffUtil.getOriginalLine(this, startLine); orgEndLine = DiffUtil.getOriginalLine(this, startLine + (numberOfLines > 0 ? 0 : -numberOfLines)) + 1; } System.out.println("orgStartLine=" + orgStartLine); System.out.println("orgEndLine =" + orgEndLine); System.out.println("revStartLine=" + revStartLine); System.out.println("revEndLine =" + revEndLine); deltaListToRemove = new ArrayList<JMDelta>(); chunkListToChange = new ArrayList<JMChunk>(); // Find the delta's of this change! endLine = startLine + Math.abs(numberOfLines); for (JMDelta delta : deltaList) { chunk = original ? delta.getOriginal() : delta.getRevised(); // The change is above this Chunk! It will not change! if (endLine < chunk.getAnchor() - 5) { continue; } // The change is below this chunk! The anchor of the chunk will be changed! if (startLine > chunk.getAnchor() + chunk.getSize() + 5) { // No need to change chunks if the numberoflines haven't changed. if (numberOfLines != 0) { chunkListToChange.add(chunk); } continue; } // This chunk is affected by the change. It will eventually be removed. // The lines that are affected will be compared and they will insert // new delta's if necessary. deltaListToRemove.add(delta); // Revise the start and end if there are overlapping chunks. chunk = delta.getOriginal(); if (chunk.getAnchor() < orgStartLine) { orgStartLine = chunk.getAnchor(); } if (chunk.getAnchor() + chunk.getSize() > orgEndLine) { orgEndLine = chunk.getAnchor() + chunk.getSize(); } chunk = delta.getRevised(); if (chunk.getAnchor() < revStartLine) { revStartLine = chunk.getAnchor(); } if (chunk.getAnchor() + chunk.getSize() > revEndLine) { revEndLine = chunk.getAnchor() + chunk.getSize(); } } orgStartLine = orgStartLine < 0 ? 0 : orgStartLine; revStartLine = revStartLine < 0 ? 0 : revStartLine; // Check with 'max' if we are dealing with the end of the file. length = Math.min(orgArray.length, orgEndLine) - orgStartLine; orgArrayDelta = new Object[length]; System.arraycopy(orgArray, orgStartLine, orgArrayDelta, 0, orgArrayDelta.length); length = Math.min(revArray.length, revEndLine) - revStartLine; revArrayDelta = new Object[length]; System.arraycopy(revArray, revStartLine, revArrayDelta, 0, revArrayDelta.length); try { for (int i = 0; i < orgArrayDelta.length; i++) { System.out.println(" org[" + i + "]:" + orgArrayDelta[i]); } for (int i = 0; i < revArrayDelta.length; i++) { System.out.println(" rev[" + i + "]:" + revArrayDelta[i]); } deltaRevision = new JMDiff().diff(orgArrayDelta, revArrayDelta, ignore); } catch (Exception ex) { ex.printStackTrace(); return false; } // OK, Make the changes now if (!deltaListToRemove.isEmpty()) { for (JMDelta delta : deltaListToRemove) { deltaList.remove(delta); } } for (JMChunk c : chunkListToChange) { c.setAnchor(c.getAnchor() + numberOfLines); } // Prepare the diff's to be copied into this revision. for (JMDelta delta : deltaRevision.deltaList) { chunk = delta.getOriginal(); chunk.setAnchor(chunk.getAnchor() + orgStartLine); chunk = delta.getRevised(); chunk.setAnchor(chunk.getAnchor() + revStartLine); } // Find insertion index point if (deltaRevision.deltaList.size() > 0) { firstDelta = deltaRevision.deltaList.get(0); index = 0; for (JMDelta delta : deltaList) { if (delta.getOriginal().getAnchor() > firstDelta.getOriginal() .getAnchor()) { break; } index++; } for (JMDelta diffDelta : deltaRevision.deltaList) { diffDelta.setRevision(this); deltaList.add(index, diffDelta); index++; } } return true; } private void insert(JMDelta delta) { int index; int anchor; index = 0; anchor = delta.getOriginal().getAnchor(); for (JMDelta d : deltaList) { if (d.getOriginal().getAnchor() > anchor) { deltaList.add(index, delta); return; } index++; } deltaList.add(delta); } private JMDelta findDelta(boolean original, int anchor, int size) { JMChunk chunk; size = size == 0 ? 1 : size; for (JMDelta delta : deltaList) { chunk = original ? delta.getOriginal() : delta.getRevised(); if (anchor >= chunk.getAnchor() && anchor <= chunk.getAnchor() + chunk.getSize()) { return delta; } if (anchor + size >= chunk.getAnchor() && anchor + size <= chunk.getAnchor() + chunk.getSize()) { return delta; } } return null; } public int getOrgSize() { return orgArray == null ? 0 : orgArray.length; } public int getRevSize() { return revArray == null ? 0 : revArray.length; } public String getOriginalString(JMChunk chunk) { return getObjects(orgArray, chunk); } public Ignore getIgnore() { return ignore; } public String getRevisedString(JMChunk chunk) { return getObjects(revArray, chunk); } private String getObjects(Object[] objects, JMChunk chunk) { Object[] result; StringBuffer sb; int end; if (chunk.getSize() <= 0) { return ""; } sb = new StringBuffer(); end = chunk.getAnchor() + chunk.getSize(); for (int offset = chunk.getAnchor(); offset < end; offset++) { sb.append(objects[offset].toString()); } return sb.toString(); } public JMRevision createChangeRevision(JMChunk original, JMChunk revised, boolean calculateDeltas) { String originalString = getOriginalString(original); String revisedString = getRevisedString(revised); try { Tokenizer t = TokenizerFactory.getInstance().getInnerDiffTokenizer(); List<String> tokensOrg = t.getTokens(originalString); List<String> tokensRev = t.getTokens(revisedString); int[] tokensOrgIndex = createIndex(tokensOrg, "tokensOrgIndex"); int[] tokensRevIndex = createIndex(tokensRev, "tokensRevIndex"); JMRevision changeRevision = new JMRevision(cloneCharacters(originalString.toCharArray()), cloneCharacters(revisedString.toCharArray())); changeRevision.setIgnore(Ignore.NULL_IGNORE); if (calculateDeltas) { JMRevision rev = new JMDiff().diff(tokensOrg, tokensRev, Ignore.NULL_IGNORE); for (JMDelta d : rev.getDeltas()) { JMDelta d2 = new JMDelta(createChunk(tokensOrgIndex, d.getOriginal()), createChunk(tokensRevIndex, d.getRevised())); changeRevision.add(d2); debug("delta = " + d + " -> " + d2); } } return changeRevision; } catch (Exception ex) { ex.printStackTrace(); } return null; } private JMChunk createChunk(int[] index, JMChunk chunk) { int anchor = chunk.getAnchor(); int size = chunk.getSize(); int oAnchor = anchor == 0 ? 0 : index[anchor - 1]; int oLength = size > 0 ? (index[anchor + size - 1] - oAnchor) : 0; return new JMChunk(oAnchor, oLength); } private int[] createIndex(List<String> tokens, final String name) { int[] index = new int[tokens.size()]; for (int i = 0; i < tokens.size(); i++) { index[i] = tokens.get(i).length(); if (i > 0) { index[i] += index[i - 1]; } debug(name + "[" + i + "] = " + index[i] + " \"" + tokens.get(i) + "\""); } return index; } private Character[] cloneCharacters(char[] chars) { Character[] chars2 = new Character[chars.length]; for (int j = 0; j < chars.length; j++) { chars2[j] = chars[j]; } return chars2; } private void debug(String s) { if (debug) { System.out.println(s); } } }