/* 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 java.util.HashMap; import java.util.Map; /** * */ public class CodeSyncAlgorithm { /** * The value used in case the model adapter does not know about its value. * It is ignored at equality check, i.e. it equals any other value, including null. * * @author Mariana */ public static final String UNDEFINED = "UNDEFINED_VALUE"; protected ModelAdapterFactorySet modelAdapterFactorySet; public CodeSyncAlgorithm(ModelAdapterFactorySet modelAdapterFactorySet) { super(); this.modelAdapterFactorySet = modelAdapterFactorySet; } public void generateDiff(Match match) { beforeOrAfterFeaturesProcessed(match, true); Object[] delegateAndAdapter = match.getDelegateAndModelAdapter(modelAdapterFactorySet); if (delegateAndAdapter == null) throw new IllegalArgumentException("A match with no members has been given as parameter."); for (Object feature : modelAdapterFactorySet.getFeatureProvider(delegateAndAdapter[0]).getFeatures(delegateAndAdapter[0])) { switch (modelAdapterFactorySet.getFeatureProvider(delegateAndAdapter[0]).getFeatureType(feature)) { case IModelAdapter.FEATURE_TYPE_CONTAINMENT: processContainmentFeature(feature, match); break; case IModelAdapter.FEATURE_TYPE_VALUE: processValueFeature(feature, match); break; } } beforeOrAfterFeaturesProcessed(match, false); } /** * Calls {@link IModelAdapter#beforeFeaturesProcessed(Object, Object)} or {@link IModelAdapter#featuresProcessed(Object)} * for the ancestor, left and right object of the <code>match</code>. * * @author Mariana */ protected void beforeOrAfterFeaturesProcessed(Match match, boolean before) { Object right = match.getRight(); IModelAdapter rightAdapter = null; if (right != null) { rightAdapter = match.getEditableResource().getModelAdapterFactorySet().getRightFactory().getModelAdapter(right); } Object ancestor = match.getAncestor(); IModelAdapter ancestorAdapter = null; if (ancestor != null) { ancestorAdapter = match.getEditableResource().getModelAdapterFactorySet().getAncestorFactory().getModelAdapter(ancestor); } Object left = match.getLeft(); IModelAdapter leftAdapter = null; if (left != null) { leftAdapter = match.getEditableResource().getModelAdapterFactorySet().getLeftFactory().getModelAdapter(left); } if (before) { if (ancestorAdapter != null) ancestorAdapter.beforeFeaturesProcessed(ancestor, right); if (leftAdapter != null) leftAdapter.beforeFeaturesProcessed(left, right); if (rightAdapter != null) rightAdapter.beforeFeaturesProcessed(right, null); } else { if (ancestorAdapter != null) ancestorAdapter.featuresProcessed(ancestor); if (leftAdapter != null) leftAdapter.featuresProcessed(left); if (rightAdapter != null) rightAdapter.featuresProcessed(right); } } /** * @author Cristi * @author Mariana * * */ public void processContainmentFeature(Object feature, Match match) { // cache the model adapters for children to avoid // a lot of calls to the model adapter factory; we are // assuming that all the children of an object, for a certain // feature, are similar (i.e. same type and same model adapter) IModelAdapter leftChildModelAdapter = null; IModelAdapter rightChildModelAdapter = null; IModelAdapter ancestorChildModelAdapter = null; // FILL_RIGHT_MAP Map<Object, Object> rightMap = new HashMap<Object, Object>(); Iterable<?> rightList = null; if (match.getRight() != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getRightFactory().getModelAdapter(match.getRight()); rightList = modelAdapter.getContainmentFeatureIterable(match.getRight(), feature, null); for (Object rightChild : rightList) { // if (rightChildModelAdapter == null) rightChildModelAdapter = modelAdapterFactorySet.getRightFactory().getModelAdapter(rightChild); // rightMap.put(rightChildModelAdapter.getMatchKey(rightChild), rightChild); if (rightChildModelAdapter != null) { rightChildModelAdapter.addToMap(rightChild, rightMap); } } } // FILL_LEFT_MAP Map<Object, Object> leftMap = new HashMap<Object, Object>(); if (match.getLeft() != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getLeftFactory().getModelAdapter(match.getLeft()); Iterable<?> leftList = modelAdapter.getContainmentFeatureIterable(match.getLeft(), feature, rightList); for (Object leftChild : leftList) { // if (leftChildModelAdapter == null) leftChildModelAdapter = modelAdapterFactorySet.getLeftFactory().getModelAdapter(leftChild); // leftMap.put(leftChildModelAdapter.getMatchKey(leftChild), leftChild); if (leftChildModelAdapter != null) { leftChildModelAdapter.addToMap(leftChild, leftMap); } } } // ITERATE_ANCESTOR_LIST if (match.getAncestor() != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getAncestorFactory().getModelAdapter(match.getAncestor()); Iterable<?> ancestorList = modelAdapter.getContainmentFeatureIterable(match.getAncestor(), feature, rightList); for (Object ancestorChild : ancestorList) { // if (ancestorChildModelAdapter == null) ancestorChildModelAdapter = modelAdapterFactorySet.getAncestorFactory().getModelAdapter(ancestorChild); if (ancestorChildModelAdapter != null) { // this will be a 3-match, 2-match or 1-match // depending on what we find in the maps Match childMatch = new Match(); childMatch.setAncestor(ancestorChild); // childMatch.setLeft(leftMap.remove(ancestorChildModelAdapter.getMatchKey(ancestorChild))); // childMatch.setRight(rightMap.remove(ancestorChildModelAdapter.getMatchKey(ancestorChild))); childMatch.setLeft(ancestorChildModelAdapter.removeFromMap(ancestorChild, leftMap, false)); childMatch.setRight(ancestorChildModelAdapter.removeFromMap(ancestorChild, rightMap, true)); childMatch.setFeature(feature); if (!childMatch.isEmptyMatch()) { match.addSubMatch(childMatch); // recurse generateDiff(childMatch); } } } } // ITERATE_REMAINING_LEFT_CHILDREN for (Object leftChild : leftMap.values()) { // this will be a 2-match or 1-match // depending on what we find in the maps Match childMatch = new Match(); childMatch.setLeft(leftChild); // childMatch.setRight(rightMap.remove(leftChildModelAdapter.getMatchKey(leftChild))); // if (leftChildModelAdapter == null) { // might be null for CodeSync/code, because the leftMap iteration doesn't happen // or if there are no ancestor children leftChildModelAdapter = modelAdapterFactorySet.getLeftFactory().getModelAdapter(leftChild); // } childMatch.setRight(leftChildModelAdapter.removeFromMap(leftChild, rightMap, true)); childMatch.setFeature(feature); if (!childMatch.isEmptyMatch()) { match.addSubMatch(childMatch); // recurse generateDiff(childMatch); } } // ITERATE_REMAINING_RIGHT_CHILDREN for (Object rightChild : rightMap.values()) { // this will be a 1-match-ancestor (i.e. deleted left & right) Match childMatch = new Match(); childMatch.setRight(rightChild); childMatch.setFeature(feature); if (!childMatch.isEmptyMatch()) { match.addSubMatch(childMatch); // recurse generateDiff(childMatch); } } } /** * @author Cristi * @author Mariana */ public static boolean safeEquals(Object a, Object b) { if (UNDEFINED.equals(a) || UNDEFINED.equals(b)) { return true; } if (a == null && b == null) return true; else if (a == null || b == null) return false; else return a.equals(b); } /** * @author Cristi * @author Mariana * * */ public void processValueFeature(Object feature, Match match) { Diff diff = null; Object ancestor = match.getAncestor(); Object left = match.getLeft(); Object right = match.getRight(); if (ancestor == null && left == null || ancestor == null && right == null || left == null && right == null) return; // for 1-Match, don't do anything Object ancestorValue = null; Object leftValue = null; Object rightValue = null; if (right != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getRightFactory().getModelAdapter(right); rightValue = modelAdapter.getValueFeatureValue(right, feature, null); } if (ancestor != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getAncestorFactory().getModelAdapter(ancestor); ancestorValue = modelAdapter.getValueFeatureValue(ancestor, feature, rightValue); } if (left != null) { IModelAdapter modelAdapter = modelAdapterFactorySet.getLeftFactory().getModelAdapter(left); leftValue = modelAdapter.getValueFeatureValue(left, feature, rightValue); } if (left != null && right != null && safeEquals(leftValue, rightValue)) { if (ancestor != null && !safeEquals(leftValue, ancestorValue)) { diff = new Diff(); diff.setLeftModified(true); diff.setRightModified(true); } } else { if (ancestor != null && left != null && safeEquals(ancestorValue, leftValue)) { // modif on RIGHT if (right != null) { diff = new Diff(); diff.setRightModified(true); } } else if (ancestor != null && right != null && safeEquals(ancestorValue, rightValue)) { // modif on LEFT if (left != null) { diff = new Diff(); diff.setLeftModified(true); } } else { diff = new Diff(); if (left != null) diff.setLeftModified(true); if (right != null) diff.setRightModified(true); diff.setConflict(true); } } if (diff != null) { diff.setFeature(feature); match.addDiff(diff); } } }