/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.diff.merge; import java.util.List; import java.util.Observable; import java.util.Vector; import org.openflexo.diff.ComputeDiff; import org.openflexo.diff.ComputeDiff.DiffChange; import org.openflexo.diff.ComputeDiff.DiffReport; import org.openflexo.diff.DelimitingMethod; import org.openflexo.diff.DiffSource; import org.openflexo.diff.merge.MergeChange.ChangeCategory; import org.openflexo.diff.merge.MergeChange.MergeChangeAction; import org.openflexo.diff.merge.MergeChange.MergeChangeResult; import org.openflexo.diff.merge.MergeChange.MergeChangeSource; import org.openflexo.diff.merge.MergeChange.MergeChangeType; public class Merge extends Observable implements IMerge { protected static final boolean debug = false; private static final MergeChangeAction DEFAULT_ACTION = MergeChangeAction.Undecided; private DiffSource _original; private DiffSource _left; private DiffSource _right; private Vector<MergeChange> changes; private MergedDocumentType _docType; public Merge(DiffSource original, DiffSource left, DiffSource right, MergedDocumentType docType) { super(); _docType = docType; DelimitingMethod delimitingMethod = original.getDelimitingMethod(); if (left.getDelimitingMethod() != delimitingMethod || right.getDelimitingMethod() != delimitingMethod) { throw new IllegalArgumentException("Cannot merge between sources with different delimiting method"); } _original = original; _left = left; _right = right; changes = new Vector<MergeChange>(); computeChanges(); } public Merge(String original, String left, String right, MergedDocumentType docType) { this(new DiffSource(original), new DiffSource(left), new DiffSource(right), docType); } protected void recompute() { changes.clear(); computeChanges(); } public void delete() { deleteObservers(); changes.clear(); _original = null; _left = null; _right = null; } public boolean isReallyConflicting() { for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChange.MergeChangeSource.Conflict) { return true; } } return false; } private boolean isResolvedNeedsRecomputing = true; private boolean isResolved; @Override public boolean isResolved() { if (isResolvedNeedsRecomputing) { isResolved = true; for (MergeChange change : getChanges()) { if (!change.isResolved()) { isResolved = false; break; } } isResolvedNeedsRecomputing = false; } return isResolved; } protected void addChange(MergeChange change) { changes.add(change); change.setMerge(this); } @Override public Vector<MergeChange> getChanges() { return changes; } @Override public int getLeftChangeCount() { int returned = 0; for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChangeSource.Left) { returned++; } } return returned; } @Override public int getRightChangeCount() { int returned = 0; for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChangeSource.Right) { returned++; } } return returned; } @Override public int getConflictsChangeCount() { int returned = 0; for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChangeSource.Conflict) { returned++; } } return returned; } @Override public int getResolvedConflictsChangeCount() { int returned = 0; for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChangeSource.Conflict) { if (change.isResolved()) { returned++; } } } return returned; } public int getUnresolvedConflictsChangeCount() { int returned = 0; for (MergeChange change : getChanges()) { if (change.getMergeChangeSource() == MergeChangeSource.Conflict) { if (!change.isResolved()) { returned++; } } } return returned; } @Override public String toString() { if (changes.size() == 0) { return "Merge: no changes"; } else { StringBuffer returned = new StringBuffer(); for (MergeChange c : getChanges()) { returned.append(c); } return returned.toString(); } } @Override public MergeChange changeBefore(MergeChange change) { int index = changes.indexOf(change); if (index >= 1) { return changes.get(index - 1); } return null; } @Override public DiffSource getLeftSource() { return _left; } @Override public DiffSource getOriginalSource() { return _original; } @Override public DiffSource getRightSource() { return _right; } public void setLeftSource(DiffSource leftSource) { _left = leftSource; recompute(); } public void setOriginalSource(DiffSource originalSource) { _original = originalSource; recompute(); } public void setRightSource(DiffSource rightSource) { _right = rightSource; recompute(); } private boolean intersect(DiffChange aLeftChange, DiffChange aRightChange) { int l1 = aLeftChange.getFirst1(); int l2 = aLeftChange.getLast1(); int r1 = aRightChange.getFirst0(); int r2 = aRightChange.getLast0(); if (l1 == r1 && l2 == r2) { return true; } return l1 >= r1 && l1 <= r2 || l2 >= r1 && l2 <= r2 || l1 <= r1 && l2 >= r2; } private DiffChange getNextLeftChangesFromLine(int lineNb, DiffReport leftReport) { for (DiffChange change : leftReport.getChanges()) { if (change.getFirst1() >= lineNb && !processedChanges.contains(change)) { return change; } } return null; } private DiffChange getNextRightChangesFromLine(int lineNb, DiffReport rightReport) { for (DiffChange change : rightReport.getChanges()) { if (change.getFirst0() >= lineNb && !processedChanges.contains(change)) { return change; } } return null; } private int leftToOriginal = 0; private int originalToRight = 0; private Vector<DiffChange> processedChanges; protected void computeChanges() { DiffReport leftReport = ComputeDiff.diff(_left, _original); DiffReport rightReport = ComputeDiff.diff(_original, _right); if (debug) { System.out.println("left-diff:\n" + leftReport); } if (debug) { System.out.println("right-diff:\n" + rightReport); } leftToOriginal = 0; originalToRight = 0; int currentLineNb = 0; int last = -1; processedChanges = new Vector<DiffChange>(); int lastProcessedChange = processedChanges.size(); boolean originalIsEmpty = _original.getTextTokens().length == 0; while (currentLineNb < _original.getTextTokens().length || originalIsEmpty) { originalIsEmpty = false; if (currentLineNb == last && lastProcessedChange == processedChanges.size()) { new Exception().printStackTrace(); System.err.println("Current:" + currentLineNb + " stopped on infinite loop"); return; } else { last = currentLineNb; lastProcessedChange = processedChanges.size(); } // DiffChange rightChange = getNextRightChangesFromLine(currentLineNb, rightReport); DiffChange leftChange = getNextLeftChangesFromLine(currentLineNb, leftReport); if (rightChange == null && leftChange == null) { currentLineNb = _original.getTextTokens().length; } else { if (debug) { System.out.println("============================="); } if (debug) { System.out.println("BEGIN currentLineNb=" + currentLineNb); } if (debug) { System.out.println("Next right=" + rightChange); } if (debug) { System.out.println("Next left=" + leftChange); } if (debug) { System.out.println("BEFORE NEW INSERTION leftToOriginal=" + leftToOriginal); } if (debug) { System.out.println("BEFORE NEW INSERTION originalToRight=" + originalToRight); } // At last one not null if (rightChange == null) { // Add left one MergeChange newChange = addLeftChange(leftChange); currentLineNb = newChange.getLast1() + 1; if (debug) { System.out.println("ADD LEFT CHANGE: " + leftChange + " > " + newChange); } } else if (leftChange == null) { // Add right one MergeChange newChange = addRightChange(rightChange); currentLineNb = newChange.getLast1() + 1; if (debug) { System.out.println("ADD RIGHT CHANGE: " + rightChange + " > " + newChange); } } else { // Still both side if (intersect(leftChange, rightChange)) { if (debug) { System.out.println("Begin MERGING : " + leftChange + " and " + rightChange); } Vector<DiffChange> leftChangesToMerge = new Vector<DiffChange>(); Vector<DiffChange> rightChangesToMerge = new Vector<DiffChange>(); leftChangesToMerge.add(leftChange); if (debug) { System.out.println("Add LEFT : " + leftChange); } rightChangesToMerge.add(rightChange); if (debug) { System.out.println("Add RIGHT : " + rightChange); } DiffChange result = new ComputeDiff.ModificationChange(); DiffChange nextRightChange; DiffChange nextLeftChange; boolean moreMerges; result.setFirst0(Math.max(leftChange.getFirst1(), rightChange.getFirst0())); result.setLast0(Math.max(leftChange.getLast1(), rightChange.getLast0())); result.setFirst1(Math.max(leftChange.getFirst1(), rightChange.getFirst0())); result.setLast1(Math.max(leftChange.getLast1(), rightChange.getLast0())); if (debug) { System.out.println("result=" + result); } do { moreMerges = false; // next right int currentLastLineRight = rightChange.getLast0() + 1; boolean foundAnOtherOne = true; while (foundAnOtherOne) { foundAnOtherOne = false; nextRightChange = getNextRightChangesFromLine(currentLastLineRight, rightReport); if (debug) { System.out.println("Examining nextRightChange: " + nextRightChange); } if (nextRightChange != null && intersect(result, nextRightChange) && nextRightChange != rightChange) { if (debug) { System.out.println("Add nextRightChange: " + nextRightChange); } rightChangesToMerge.add(nextRightChange); rightChange = nextRightChange; currentLastLineRight = rightChange.getLast0(); moreMerges = true; result.setLast0(Math.max(result.getLast0(), rightChange.getLast0())); result.setLast1(result.getLast0()); foundAnOtherOne = true; } } // next left int currentLastLineLeft = leftChange.getLast1() + 1; foundAnOtherOne = true; while (foundAnOtherOne) { foundAnOtherOne = false; nextLeftChange = getNextLeftChangesFromLine(currentLastLineLeft, leftReport); if (debug) { System.out.println("Examining nextLeftChange: " + nextLeftChange); } if (nextLeftChange != null && intersect(nextLeftChange, result) && nextLeftChange != leftChange) { if (debug) { System.out.println("Add nextLeftChange: " + nextLeftChange); } leftChangesToMerge.add(nextLeftChange); leftChange = nextLeftChange; currentLastLineLeft = leftChange.getLast1(); moreMerges = true; result.setLast1(Math.max(result.getLast1(), leftChange.getLast1())); result.setLast0(result.getLast1()); foundAnOtherOne = true; } } } while (moreMerges); MergeChange newChange = addMergedChanges(leftChangesToMerge, rightChangesToMerge); currentLineNb = newChange.getLast1() + 1; if (debug) { System.out.println("ADD MERGED CHANGES: > " + newChange); } } else { if (leftChange.getFirst1() <= rightChange.getFirst0()) { MergeChange newChange = addLeftChange(leftChange); currentLineNb = newChange.getLast1() + 1; if (debug) { System.out.println("ADD LEFT CHANGE: " + leftChange + " > " + newChange); } } else { MergeChange newChange = addRightChange(rightChange); currentLineNb = newChange.getLast1() + 1; if (debug) { System.out.println("ADD RIGHT CHANGE: " + rightChange + " > " + newChange); } } } } } if (debug) { System.out.println("AFTER NEW INSERTION leftToOriginal=" + leftToOriginal); } if (debug) { System.out.println("AFTER NEW INSERTION originalToRight=" + originalToRight); } if (debug) { System.out.println("END currentLineNb=" + currentLineNb); } } mergeNeedsRecomputing = true; // System.err.println("left: "+getLeftSource().hashCode()); // System.err.println("left: "+this.hashCode()+" "+getLeftSource().getSourceString()); setChanged(); notifyObservers(new MergeRecomputed()); } public class MergeRecomputed { } private MergeChange addLeftChange(DiffChange leftChange) { MergeChange newChange = MergeChange.makeLeftMergeChange(leftChange, originalToRight); processedChanges.add(leftChange); newChange.setMergeChangeSource(MergeChange.MergeChangeSource.Left); addChange(newChange); leftToOriginal += leftChange.getLast1() - leftChange.getFirst1() - (leftChange.getLast0() - leftChange.getFirst0()); newChange.setDebug("[" + leftChange.toNiceString(false) + "], leftToOriginal=" + leftToOriginal + " originalToRight=" + originalToRight); // return leftChange.getLast1()+1/*-leftToOriginal*/; return newChange; } private MergeChange addRightChange(DiffChange rightChange) { MergeChange newChange = MergeChange.makeRightMergeChange(rightChange, leftToOriginal); processedChanges.add(rightChange); newChange.setMergeChangeSource(MergeChange.MergeChangeSource.Right); addChange(newChange); originalToRight += rightChange.getLast0() - rightChange.getFirst0() - (rightChange.getLast1() - rightChange.getFirst1()); newChange.setDebug("[" + rightChange.toNiceString(false) + "], leftToOriginal=" + leftToOriginal + " originalToRight=" + originalToRight); // return rightChange.getLast0()+1/*-originalToRight*/; return newChange; } private MergeChange addMergedChanges(Vector<DiffChange> leftVector, Vector<DiffChange> rightVector) { String mergeDebug = ""; boolean isFirst = true; MergeChangeAction defaultAction; if (getDocumentType() != null && getDocumentType().getAutomaticMergeResolvingModel() != null) { defaultAction = MergeChangeAction.AutomaticMergeResolving; } else { defaultAction = DEFAULT_ACTION; } MergeChange leftMerges = MergeChange.makeLeftMergeChange(leftVector, originalToRight, rightVector, leftToOriginal, defaultAction); for (DiffChange change : leftVector) { processedChanges.add(change); MergeChange debug = MergeChange.makeLeftMergeChange(change, originalToRight); mergeDebug += (isFirst ? "" : ",") + debug.toNiceString(); isFirst = false; } MergeChange rightMerges = MergeChange.makeRightMergeChange(rightVector, leftToOriginal, leftVector, originalToRight, defaultAction); for (DiffChange change : rightVector) { processedChanges.add(change); MergeChange debug = MergeChange.makeRightMergeChange(change, leftToOriginal); mergeDebug += "," + debug.toNiceString(); } if (debug) { System.out.println("Left merges: " + leftMerges); } if (debug) { System.out.println("Right merges: " + rightMerges); } // Special case when merging a deletion from the right if (rightMerges.getFirst2() > rightMerges.getLast2()) { leftMerges.setFirst2(rightMerges.getFirst2()); leftMerges.setLast2(rightMerges.getLast2()); } // Special case when merging a deletion from the left if (leftMerges.getFirst0() > leftMerges.getLast0()) { rightMerges.setFirst0(leftMerges.getFirst0()); rightMerges.setLast0(leftMerges.getLast0()); } Vector<MergeChange> both = new Vector<MergeChange>(); both.add(leftMerges); both.add(rightMerges); MergeChange newChange = MergeChange.makeMergeChange(both, defaultAction); originalToRight += rightMerges.getLast1() - rightMerges.getFirst1() - (rightMerges.getLast2() - rightMerges.getFirst2());// +leftMerges.delta; leftToOriginal += leftMerges.getLast1() - leftMerges.getFirst1() - (leftMerges.getLast0() - leftMerges.getFirst0());// +rightMerges.delta; newChange.setMergeChangeSource(MergeChangeSource.Conflict); newChange.setMergeChangeType(MergeChangeType.Modification); addChange(newChange); newChange.setDebug("[" + mergeDebug + "], leftToOriginal=" + leftToOriginal + " originalToRight=" + originalToRight); return newChange; } protected void notifyMergeChange(MergeChange change) { isResolvedNeedsRecomputing = true; updateMergedSource(); // System.out.println("On a choisi une nouvelle action pour le merge "+hashCode()); setChanged(); notifyObservers(change); } private boolean mergeNeedsRecomputing = true; private DiffSource _merge; @Override public synchronized DiffSource getMergedSource() { if (mergeNeedsRecomputing) { updateMergedSource(); } // System.out.println("_merge.getSourceString() pour "+hashCode()+" :\n"+_merge.getSourceString()); return _merge; } @Override public String getMergedText() { return getMergedSource().getText(); } private synchronized void updateMergedSource() { String mergeAsString = computeMerge(); // System.out.println("mergeAsString="+mergeAsString); if (_merge == null) { _merge = new DiffSource(mergeAsString, getDelimitingMethod()); } else { _merge.updateWith(mergeAsString); } mergeNeedsRecomputing = false; } private String computeMerge() { if (debug) { System.out.println("computeMerge() in Merge"); } StringBuffer sb = new StringBuffer(); int currentLine = 0; int addedLines = 0; for (MergeChange change : getChanges()) { appendLines(sb, currentLine, change.getFirst1()); addedLines += change.getFirst1() - currentLine; MergeChangeResult changeResult = change.getMergeChangeResult(); sb.append(changeResult.merge); // System.out.println("change.getMergeChangeResult():================\n"+"Tokens: "+changeResult.tokensNb+"\n"+changeResult.merge); /*String[] changeResult = change.getMergeChangeResult(); appendLines (sb,changeResult);*/ change.setFirstMergeIndex(addedLines); change.setLastMergeIndex(addedLines + changeResult.tokensNb - 1); addedLines += changeResult.tokensNb; currentLine = change.getLast1() + 1; } appendLines(sb, currentLine, getOriginalSource().getTextTokens().length); addedLines += getOriginalSource().getTextTokens().length - currentLine; return sb.toString(); } private void appendLines(StringBuffer sb, int begin, int end) { for (int i = begin; i < end; i++) { sb.append(getOriginalSource().tokenAt(i).getFullString()); } } // Overriden in DetailedMerge public DelimitingMethod getDelimitingMethod() { return DelimitingMethod.LINES; } public MergedDocumentType getDocumentType() { return _docType; } @Override public Vector<MergeChange> filteredChangeList(List<ChangeCategory> selectedCategories) { Vector<MergeChange> reply = new Vector<MergeChange>(); for (MergeChange item : changes) { if (selectedCategories.contains(item.category())) { reply.add(item); } } return reply; } public static void main(String[] args) { DiffSource orig = new DiffSource("{\n" + " \"WebObjects Release\" = \"WebObjects 5.0\";\n" + " encoding = NSUTF8StringEncoding;\n" + " variables = {};\n" + "}", DelimitingMethod.PLIST); DiffSource left = new DiffSource("{\n" + " \"WebObjects Release\" = \"WebObjects 5.0\";\n" + " encoding = NSUTF8StringEncoding;\n" + " variables = {};\n" + "}", DelimitingMethod.PLIST); DiffSource right = new DiffSource("{\n" + " \"WebObjects Release\" = \"WebObjects 5.0\";\n" + " encoding = NSUTF8StringEncoding;\n" + " variables = {};\n" + "}", DelimitingMethod.PLIST); Merge merge = new Merge(orig, left, right, DefaultMergedDocumentType.PLIST); } }