/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program 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 version 3. * * This program 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, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package com.crispico.flower.mp.codesync.wiki; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.eclipse.compare.internal.DocLineComparator; import org.eclipse.compare.rangedifferencer.RangeDifference; import org.eclipse.compare.rangedifferencer.RangeDifferencer; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.flowerplatform.model.astcache.wiki.AstCacheWikiPackage; import org.flowerplatform.model.astcache.wiki.FlowerBlock; import com.crispico.flower.mp.codesync.base.Diff; import com.crispico.flower.mp.model.codesync.CodeSyncElement; /** * @author Mariana */ public class WikiDiff extends Diff { public enum Direction { FROM_ANCESTOR_TO_LEFT, FROM_ANCESTOR_TO_RIGHT, FROM_LEFT_TO_RIGHT, FROM_RIGHT_TO_LEFT } private RangeDifference[] diffs; private boolean leftResolved, rightResolved; private IDocument ancestor, left, right; private TextEdit leftEdit = new MultiTextEdit(), rightEdit = new MultiTextEdit(); private List<FlowerBlock> flowerBlocks = new ArrayList<FlowerBlock>(); private String technology; public WikiDiff(String ancestor, String left, CodeSyncElement right, String technology) { this.ancestor = new Document(ancestor); this.left = new Document(left); this.right = new Document(WikiPlugin.getInstance().getWikiText(right, technology)); long time = new Date().getTime(); // compute range differences boolean ignoreWhiteSpace = false; diffs = RangeDifferencer.findRanges( new DocLineComparator(this.ancestor, null, ignoreWhiteSpace), new DocLineComparator(this.left, null, ignoreWhiteSpace), new DocLineComparator(this.right, null, ignoreWhiteSpace)); setFeature(AstCacheWikiPackage.eINSTANCE.getPage_InitialContent()); this.technology = technology; // mark conflicted flower blocks if (right != null) { getFlowerBlocks(right, flowerBlocks); } for (RangeDifference diff : diffs) { switch (diff.kind()) { case RangeDifference.CONFLICT: // if the conflict is in a flower block, ignore it, but mark the node boolean conflictInFlowerBlock = false; for (FlowerBlock block : flowerBlocks) { if (overlap(block, diff)) { block.setConflict(true); conflictInFlowerBlock = true; } } if (!conflictInFlowerBlock) { setConflict(true); setLeftModified(true); setRightModified(true); } break; case RangeDifference.LEFT: setLeftModified(true); break; case RangeDifference.RIGHT: setRightModified(true); break; case RangeDifference.ANCESTOR: setLeftModified(true); setRightModified(true); break; } } } public WikiDiff(WikiDiff other) { this.ancestor = other.ancestor; this.left = other.left; this.right = other.right; this.flowerBlocks = other.flowerBlocks; this.technology = other.technology; this.diffs = other.diffs; this.leftEdit = other.leftEdit; this.rightEdit = other.rightEdit; this.leftResolved = other.leftResolved; this.rightResolved = other.rightResolved; this.setLeftModified(other.isLeftModified()); this.setRightModified(other.isRightModified()); this.setConflict(other.isConflict()); this.setFeature(other.getFeature()); this.setParentMatch(other.getParentMatch()); } public RangeDifference[] getDiffs() { return diffs; } /** * Returns the content of the left document, after being transformed to a wiki tree using the {@link #flowerBlocks}. * This is to correctly synchronize any existing Flower blocks on the wiki page. */ public String getLeft() { save(true); String wikiText = left.get(); CodeSyncElement root = (CodeSyncElement) getParentMatch().getLeft(); WikiPlugin.getInstance().getWikiPageTree(wikiText, root, technology, flowerBlocks); return WikiPlugin.getInstance().getWikiText(root, technology); } /** * Creates the page nodes corresponding to the content in the right document, rooted at the given * {@link CodeSyncElement}. */ public void getRight(CodeSyncElement page) { save(false); WikiPlugin.getInstance().getWikiPageTree(right.get(), page, technology, flowerBlocks); } public boolean applyAll(CodeSyncElement page, boolean leftToRight) { if (isResolved(leftToRight)) { return true; } boolean rslt = true; for (RangeDifference diff : diffs) { if (diff.kind() != RangeDifference.NOCHANGE && diff.kind() != RangeDifference.CONFLICT) { Direction direction = getDefaultDirection(diff); if (allowDirection(direction, leftToRight)) { rslt = apply(diff, direction) && rslt; } } } markResolved(leftToRight); // trigger the creation of page children this.getRight(page); return rslt; } private boolean isResolved(boolean leftToRight) { if (leftToRight) { return rightResolved; } else { return leftResolved; } } private void markResolved(boolean leftToRight) { if (leftToRight) { rightResolved = true; } else { leftResolved = true; } } private boolean allowDirection(Direction direction, boolean leftToRight) { if (direction == null) { return false; } if (leftToRight) { return direction == Direction.FROM_ANCESTOR_TO_RIGHT || direction == Direction.FROM_LEFT_TO_RIGHT; } else { return direction == Direction.FROM_ANCESTOR_TO_LEFT || direction == Direction.FROM_RIGHT_TO_LEFT; } } public boolean apply(RangeDifference diff, Direction direction) { if (direction == null) { return true; // nothing to do } DocumentAndPosition from = getFromPosition(diff, direction); DocumentAndPosition to = getToPosition(diff, direction); createAndAddTextEdit(from, to); return true; } private void save(boolean onLeft) { try { if (onLeft) { leftEdit.apply(left); leftEdit = new MultiTextEdit(); } else { rightEdit.apply(right); rightEdit = new MultiTextEdit(); } } catch (Exception e) { } } @Override public String toString() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < diffs.length; i++) { RangeDifference diff = diffs[i]; builder.append(String.format("\n%s\n", diff)); switch (diff.kind()) { case RangeDifference.NOCHANGE: builder.append(getText(ancestor, diff.ancestorStart(), diff.ancestorLength())); break; case RangeDifference.LEFT: builder.append(String.format("\n>>>>>>> start insert \n%s\n>>>>>>> end insert\n", getText(left, diff.leftStart(), diff.leftLength()))); builder.append(String.format("\n<<<<<<< start delete \n%s\n<<<<<<< end delete\n", getText(right, diff.rightStart(), diff.rightLength()))); break; case RangeDifference.RIGHT: builder.append(String.format("\n<<<<<<< start insert \n%s\n<<<<<<< end insert\n", getText(right, diff.rightStart(), diff.rightLength()))); builder.append(String.format("\n>>>>>>> start delete \n%s\n>>>>>>> end delete\n", getText(left, diff.leftStart(), diff.leftLength()))); break; case RangeDifference.ANCESTOR: builder.append(String.format("\n+++++++ start insert \n%s\n+++++++ end insert\n", getText(left, diff.leftStart(), diff.leftLength()))); builder.append(String.format("\n------- start delete \n%s\n------- end delete\n", getText(ancestor, diff.ancestorStart(), diff.ancestorLength()))); break; case RangeDifference.CONFLICT: String conflict = String.format("\n>>>>>>>\n%s\n>>>>>>>\n<<<<<<<\n%s\n<<<<<<<\n", getText(left, diff.leftStart(), diff.leftLength()), getText(right, diff.rightStart(), diff.rightLength())); builder.append(String.format("\n******* start conflict \n%s\n******* end conflict\n", conflict)); } } return builder.toString(); } private String getText(IDocument document, int start, int length) { try { Position position = getPosition(document, start, length); return document.get(position.getOffset(), position.getLength()); } catch (BadLocationException e) { return ""; } } private void createAndAddTextEdit(DocumentAndPosition from, DocumentAndPosition to) { try { if (to.position.length == 0) { to.edit.addChild(new InsertEdit(to.position.getOffset(), from.document.get(from.position.getOffset(), from.position.getLength()))); } else { if (from.position.length == 0) { to.edit.addChild(new DeleteEdit(to.position.getOffset(), to.position.getLength())); } else { to.edit.addChild(new ReplaceEdit(to.position.getOffset(), to.position.getLength(), from.document.get(from.position.getOffset(), from.position.getLength()))); } } } catch (BadLocationException e) { } } private DocumentAndPosition getFromPosition(RangeDifference diff, Direction direction) { if (Direction.FROM_ANCESTOR_TO_LEFT == direction || Direction.FROM_ANCESTOR_TO_RIGHT == direction) { return new DocumentAndPosition(ancestor, null, getPosition(ancestor, diff.ancestorStart(), diff.ancestorLength())); } if (Direction.FROM_LEFT_TO_RIGHT == direction) { return new DocumentAndPosition(left, leftEdit, getPosition(left, diff.leftStart(), diff.leftLength())); } if (Direction.FROM_RIGHT_TO_LEFT == direction) { return new DocumentAndPosition(right, rightEdit, getPosition(right, diff.rightStart(), diff.rightLength())); } return null; } private DocumentAndPosition getToPosition(RangeDifference diff, Direction direction) { if (Direction.FROM_ANCESTOR_TO_LEFT == direction || Direction.FROM_RIGHT_TO_LEFT == direction) { return new DocumentAndPosition(left, leftEdit, getPosition(left, diff.leftStart(), diff.leftLength())); } if (Direction.FROM_ANCESTOR_TO_RIGHT == direction || Direction.FROM_LEFT_TO_RIGHT == direction) { return new DocumentAndPosition(right, rightEdit, getPosition(right, diff.rightStart(), diff.rightLength())); } return null; } private Position getPosition(IDocument document, int start, int length) { try { int offset = document.getLineOffset(start); int end = offset; if (length > 0) { int lastLine = Math.min(start + length - 1, document.getNumberOfLines() - 1); end = document.getLineOffset(lastLine) + document.getLineLength(lastLine); } return new Position(offset, end - offset); } catch (BadLocationException e) { return new Position(0, 0); } } private Direction getDefaultDirection(RangeDifference diff) { switch (diff.kind()) { case RangeDifference.LEFT: return Direction.FROM_LEFT_TO_RIGHT; case RangeDifference.RIGHT: case RangeDifference.CONFLICT: return Direction.FROM_RIGHT_TO_LEFT; default: return null; } } private void getFlowerBlocks(CodeSyncElement node, List<FlowerBlock> blocks) { if (node.getAstCacheElement() instanceof FlowerBlock) { blocks.add((FlowerBlock) node.getAstCacheElement()); } for (CodeSyncElement child : node.getChildren()) { getFlowerBlocks(child, blocks); } } private boolean overlap(FlowerBlock block, RangeDifference diff) { if (block.getLineStart() > diff.rightStart()) { return false; } if (block.getLineEnd() < diff.rightEnd()) { return false; } return true; } class DocumentAndPosition { public IDocument document; public TextEdit edit; public Position position; public DocumentAndPosition(IDocument document, TextEdit edit, Position position) { this.document = document; this.edit = edit; this.position = position; } } }