/* 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.base; import static com.crispico.flower.mp.codesync.base.CodeSyncAlgorithm.UNDEFINED; import java.util.ArrayList; import java.util.List; /** * */ public class Match { /** * @author Mariana */ public enum MatchType { _3MATCH, _2MATCH_ANCESTOR_LEFT, _2MATCH_ANCESTOR_RIGHT, _2MATCH_LEFT_RIGHT, _1MATCH_ANCESTOR, _1MATCH_LEFT, _1MATCH_RIGHT } private CodeSyncEditableResource editableResource; /** * */ private Match parentMatch; private List<Match> subMatches; private Object feature; private Object ancestor; private Object left; private Object right; private List<Diff> diffs; /** * This field should be used only in read mode. The add * should be done using {@link #addSubMatch()}. * * */ private boolean diffsConflict; private boolean diffsModifiedLeft; private boolean diffsModifiedRight; private boolean childrenModifiedLeft; private boolean childrenModifiedRight; /** * */ private boolean childrenConflict; public Match getParentMatch() { return parentMatch; } public void setParentMatch(Match parentMatch) { this.parentMatch = parentMatch; } // ***************************************************** // Getters and Setters. // ***************************************************** /** * This field should be used only in read mode. The add * should be done using {@link #addSubMatch()}. */ public List<Match> getSubMatches() { if (subMatches == null) subMatches = new ArrayList<Match>(); return subMatches; } public Object getAncestor() { return ancestor; } public void setAncestor(Object ancestor) { this.ancestor = ancestor; } public Object getLeft() { return left; } public void setLeft(Object left) { this.left = left; } public Object getRight() { return right; } public void setRight(Object right) { this.right = right; } /** * This field should be used only in read mode. The add * should be done using {@link #addDiff()}. */ public List<Diff> getDiffs() { if (diffs == null) diffs = new ArrayList<Diff>(); return diffs; } public boolean isChildrenModifiedLeft() { return childrenModifiedLeft; } public void setChildrenModifiedLeft(boolean childrenModifiedLeft) { this.childrenModifiedLeft = childrenModifiedLeft; } public boolean isChildrenModifiedRight() { return childrenModifiedRight; } public void setChildrenModifiedRight(boolean childrenModifiedRight) { this.childrenModifiedRight = childrenModifiedRight; } public boolean isChildrenConflict() { return childrenConflict; } public void setChildrenConflict(boolean childrenConflict) { this.childrenConflict = childrenConflict; } public boolean isDiffsConflict() { return diffsConflict; } public void setDiffsConflict(boolean diffsConflict) { this.diffsConflict = diffsConflict; } public Object getFeature() { return feature; } public void setFeature(Object feature) { this.feature = feature; } public boolean isDiffsModifiedLeft() { return diffsModifiedLeft; } public void setDiffsModifiedLeft(boolean diffsModifiedLeft) { this.diffsModifiedLeft = diffsModifiedLeft; } public boolean isDiffsModifiedRight() { return diffsModifiedRight; } public void setDiffsModifiedRight(boolean diffsModifiedRight) { this.diffsModifiedRight = diffsModifiedRight; } // ***************************************************** // Methods with some logic. // ***************************************************** public Object getDelegate() { if (getAncestor() != null) return getAncestor(); else if (getLeft() != null) return getLeft(); else return getRight(); } /** * @author Cristi * @author Mariana */ public Object[] getDelegateAndModelAdapter(ModelAdapterFactorySet factorySet) { Object delegate = null; IModelAdapter modelAdapter = null; if (getAncestor() != null && !getAncestor().equals(CodeSyncAlgorithm.UNDEFINED)) { delegate = getAncestor(); modelAdapter = factorySet.getAncestorFactory().getModelAdapter(delegate); } else if (getLeft() != null && !getLeft().equals(CodeSyncAlgorithm.UNDEFINED)) { delegate = getLeft(); modelAdapter = factorySet.getLeftFactory().getModelAdapter(delegate); } else if (getRight() != null && !getRight().equals(CodeSyncAlgorithm.UNDEFINED)) { delegate = getRight(); modelAdapter = factorySet.getRightFactory().getModelAdapter(delegate); } if (delegate == null) return null; else return new Object[] { delegate, modelAdapter }; } /** * Calculated. * * */ public boolean isLeftAdd() { return getAncestor() == null && getLeft() != null; } public boolean isRightAdd() { return getAncestor() == null && getRight() != null; } public boolean isLeftRemove() { return getAncestor() != null && getLeft() == null; } public boolean isRightRemove() { return getAncestor() != null && getRight() == null; } /** * Calculated. Returns <code>true</code> if: * * <ul> * <li>at least one of the {@link #diffs} has a conflict (fast * access using {@link #diffsConflict}; OR * <li>this is a 1-match-l/r and parent is a 2-match * </ul> * * */ public boolean isConflict() { return isDiffsConflict() || getAncestor() == null && (getLeft() == null || getRight() == null) && // this is 1-match-l/r AND getParentMatch() != null && ((getParentMatch().getAncestor() == null && (getParentMatch().getLeft() != null && getParentMatch().getRight() != null)) || // parent match is 2-match (getParentMatch().getAncestor() != null && (getParentMatch().getLeft() == null || getParentMatch().getRight() == null))); } public boolean isModifiedLeft() { return diffsModifiedLeft || getAncestor() != null && getLeft() == null || getAncestor() == null && getLeft() != null; } public boolean isModifiedRight() { return diffsModifiedRight || getAncestor() != null && getRight() == null || getAncestor() == null && getRight() != null; } /** * Called by the CodeSync algorithm after a new * {@link Match} has been created and initialized. Adds the * submatch to the list and sets the parent. * * <ul> * <li>Propagates {@link #childrenModifiedLeft} and {@link #childrenModifiedRight} * if the submatch is not a 3-match. * <li>Propagates {@link #childrenConflict} if this is a 2-match * and the submatch is a 1-match-left/right. * </ul> * * @param subMatch A fully initialized {@link Match}, without submatches * or diffs. * */ public void addSubMatch(Match subMatch) { getSubMatches().add(subMatch); subMatch.parentMatch = this; int nMatch = 0; if (getAncestor() != null) nMatch++; if (getLeft() != null) nMatch++; if (getRight() != null) nMatch++; boolean conflict = nMatch == 2 && // this is a 2-match subMatch.getAncestor() == null && // and new child is 1-match (subMatch.getLeft() == null || subMatch.getRight() == null); // left or right boolean modifiedLeft = false, modifiedRight = false; if (subMatch.getAncestor() != null) { if (subMatch.getLeft() == null) modifiedLeft = true; if (subMatch.getRight() == null) modifiedRight = true; } else { if (subMatch.getLeft() != null) modifiedLeft = true; if (subMatch.getRight() != null) modifiedRight = true; } propagateConflictAndModified(this, conflict, modifiedLeft, modifiedRight); } /** * Iterative propagation in parallel for conflict and modified. * */ private void propagateConflictAndModified(Match currentMatch, boolean conflict, boolean modifiedLeft, boolean modifiedRight) { while (currentMatch != null && (conflict && !currentMatch.isChildrenConflict() || // conflict not yet propagated on this node or modifiedLeft && !currentMatch.isChildrenModifiedLeft() || modifiedRight && !currentMatch.isChildrenModifiedRight())) { // modified not yet propagated if (conflict && !currentMatch.isChildrenConflict()) currentMatch.childrenConflict = true; if (modifiedLeft && !currentMatch.isChildrenModifiedLeft()) currentMatch.childrenModifiedLeft = true; if (modifiedRight && !currentMatch.isChildrenModifiedRight()) currentMatch.childrenModifiedRight = true; currentMatch = currentMatch.getParentMatch(); } } public boolean refreshDiffFlags(boolean conflict, boolean modifiedLeft, boolean modifiedRight) { boolean modified = false; if (!diffsConflict && conflict) { diffsConflict = true; modified = true; } else if (diffsConflict && !conflict) { boolean ok = true; for (Diff childDiff : getDiffs()) if (childDiff.isConflict()) { ok = false; break; } if (ok) { diffsConflict = false; modified = true; } } if (!diffsModifiedLeft && modifiedLeft) { diffsModifiedLeft = true; modified = true; } else if (diffsModifiedLeft && !modifiedLeft) { boolean ok = true; for (Diff childDiff : getDiffs()) if (childDiff.isLeftModified()) { ok = false; break; } if (ok) { diffsModifiedLeft = false; modified = true; } } if (!diffsModifiedRight && modifiedRight) { diffsModifiedRight = true; modified = true; } else if (diffsModifiedRight && !modifiedRight) { boolean ok = true; for (Diff childDiff : getDiffs()) if (childDiff.isRightModified()) { ok = false; break; } if (ok) { diffsModifiedRight = false; modified = true; } } return modified; } public List<Match> propagateConflictAndModifiedTrueOrFalse(Match currentMatch, boolean conflict, boolean modifiedLeft, boolean modifiedRight) { List<Match> modifiedMatches = new ArrayList<Match>(); while (currentMatch != null && (conflict != currentMatch.isChildrenConflict() || // conflict not yet propagated on this node or modifiedLeft != currentMatch.isChildrenModifiedLeft() || modifiedRight != currentMatch.isChildrenModifiedRight())) { // modified not yet propagated boolean modified = false; if (conflict && !currentMatch.isChildrenConflict()) { currentMatch.childrenConflict = true; modified = true; } else if (!conflict && currentMatch.isChildrenConflict()) { boolean ok = true; for (Match childMatch : currentMatch.getSubMatches()) if (childMatch.isConflict() || childMatch.isChildrenConflict()) { ok = false; break; } if (ok) { currentMatch.childrenConflict = false; modified = true; } } if (modifiedLeft && !currentMatch.isChildrenModifiedLeft()) { currentMatch.childrenModifiedLeft = true; modified = true; } else if (!modifiedLeft && currentMatch.isChildrenModifiedLeft()) { boolean ok = true; for (Match childMatch : currentMatch.getSubMatches()) if (childMatch.isModifiedLeft() || childMatch.isChildrenModifiedLeft()) { ok = false; break; } if (ok) { currentMatch.childrenModifiedLeft = false; modified = true; } } if (modifiedRight && !currentMatch.isChildrenModifiedRight()) { currentMatch.childrenModifiedRight = true; modified = true; } else if (!modifiedRight && currentMatch.isChildrenModifiedRight()) { boolean ok = true; for (Match childMatch : currentMatch.getSubMatches()) if (childMatch.isModifiedRight() || childMatch.isChildrenModifiedRight()) { ok = false; break; } if (ok) { currentMatch.childrenModifiedRight = false; modified = true; } } if (modified) modifiedMatches.add(currentMatch); currentMatch = currentMatch.getParentMatch(); } return modifiedMatches; } /** * Called by the CodeSync algorithm after a new * {@link Diff} has been created and initialized. Adds the * diff to the list and sets the parent. * * <ul> * <li>Sets the {@link #diffsConflict}. * <li>Propagates {@link #childrenModifiedLeft} and {@link #childrenModifiedRight}. * <li>Propagates {@link #childrenConflict} if the * diff has a conflict. * </ul> * * @param diff A fully initialized {@link Diff}. * */ public void addDiff(Diff diff) { getDiffs().add(diff); diff.setParentMatch(this); if (diff.isConflict()) diffsConflict = true; if (diff.isLeftModified()) diffsModifiedLeft = true; if (diff.isRightModified()) diffsModifiedRight = true; if (getParentMatch() != null) propagateConflictAndModified(getParentMatch(), diffsConflict, diffsModifiedLeft, diffsModifiedRight); } /** * @author Mariana */ public CodeSyncEditableResource getEditableResource() { Match root = this; while (root.getParentMatch() != null) { root = root.getParentMatch(); } return root.editableResource; } /** * @author Mariana */ public void setEditableResource(CodeSyncEditableResource editableResource) { this.editableResource = editableResource; } /** * @author Mariana */ public MatchType getMatchType() { if (getAncestor() != null) if (getLeft() != null) if (getRight() != null) return MatchType._3MATCH; else return MatchType._2MATCH_ANCESTOR_LEFT; else if (getRight() != null) return MatchType._2MATCH_ANCESTOR_RIGHT; else return MatchType._1MATCH_ANCESTOR; else if (getLeft() != null) if (getRight() != null) return MatchType._2MATCH_LEFT_RIGHT; else return MatchType._1MATCH_LEFT; else if (getRight() != null) return MatchType._1MATCH_RIGHT; else return null; } /** * @author Mariana */ public boolean isEmptyMatch() { if (getAncestor() != null && !getAncestor().equals(UNDEFINED)) { return false; } if (getLeft() != null && !getLeft().equals(UNDEFINED)) { return false; } if (getRight() != null && !getRight().equals(UNDEFINED)) { return false; } return true; } }