/* * (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.xml.diff3; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Vector; import org.jdom2.Attribute; import org.jdom2.Content; import org.jdom2.Element; import org.openflexo.xml.diff2.AttributesDiff; import org.openflexo.xml.diff3.mergerule.AverageLocation; import org.openflexo.xml.diff3.mergerule.AveragePosition; import org.openflexo.xml.diff3.mergerule.MaxValue; import org.openflexo.xml.diff3.mergerule.MergeAttributeRule; import org.openflexo.xml.diff3.mergerule.NewestDate; import org.openflexo.xml.diff3.mergerule.OldestDate; public class AttributesChange { private AttributesDiff _diff1; private AttributesDiff _diff2; private Vector<MergeAttributeAction> _mergeAttributesActions; private Vector<UnresolvedAttributesConflict> _unresolvedAttributesConflict; private Hashtable<UnresolvedAttributesConflict, MergeAttributeAction> _autoResolvedAttributesConflict; private Element _sourceElement; private Element _mergedElement; private XMLDiff3 _merge; public AttributesChange(XMLDiff3 merge, AttributesDiff diff1, AttributesDiff diff2, Element sourceElement, Element mergedElement) { super(); _merge = merge; _diff1 = diff1; _diff2 = diff2; _mergedElement = mergedElement; _mergeAttributesActions = new Vector<MergeAttributeAction>(); _autoResolvedAttributesConflict = new Hashtable<UnresolvedAttributesConflict, MergeAttributeAction>(); _unresolvedAttributesConflict = new Vector<UnresolvedAttributesConflict>(); _sourceElement = sourceElement; processToAdditionOfAttributes(); processToUpdateOfAttributes(); processToDeletionOfAttributes(); } public boolean hasUnresolvedConflict() { return _unresolvedAttributesConflict.size() > 0; } public Vector<UnresolvedAttributesConflict> getUnresolvedConflicts() { return _unresolvedAttributesConflict; } public Hashtable<UnresolvedAttributesConflict, MergeAttributeAction> getAutoResolvedConflicts() { return _autoResolvedAttributesConflict; } public Element getMergedElement() { if (_sourceElement.getAttributes() != null) { Iterator<Attribute> it = _sourceElement.getAttributes().iterator(); Attribute item = null; while (it.hasNext()) { item = it.next(); _mergedElement.setAttribute(item.getName(), item.getValue()); } } Enumeration<MergeAttributeAction> en = _mergeAttributesActions.elements(); while (en.hasMoreElements()) { en.nextElement().execute(); } en = _autoResolvedAttributesConflict.elements(); while (en.hasMoreElements()) { en.nextElement().execute(); } return _mergedElement; } private void processToAdditionOfAttributes() { for (String attributeName : _diff1.getAddedAttributes().keySet()) { if (_diff2.getAddedAttributes().get(attributeName) == null) { // here we have test if the added attribute isn't in a deleted part of the tree if (elementIsInOneOfThoseTree(_diff1.getSourceElement(), _diff2.getDocumentMapping().getRemovedElements())) { // here we have a conflict : the modified attribute is in a part of the tree that has been deleted UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, _diff1 .getAddedAttributes().get(attributeName), null, _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheAdditionDeleteConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually solve the conflict :-( _unresolvedAttributesConflict.add(conflict); } } else { // non conflicting addition _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.INSERT, attributeName, _diff1 .getAddedAttributes().get(attributeName).getValue(), _mergedElement)); } } else { // the same attribute was added on both sides if (_diff2.getAddedAttributes().get(attributeName).getValue() .equals(_diff1.getAddedAttributes().get(attributeName).getValue())) { // ouf : the same value has been set on both side _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.INSERT, attributeName, _diff1 .getAddedAttributes().get(attributeName).getValue(), _mergedElement)); } else { // !!! WE HAVE A CONFLICT !!! // same attribute added on both side with different values !!! UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, _diff1 .getAddedAttributes().get(attributeName), _diff2.getAddedAttributes().get(attributeName), _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheAdditionConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually solve the conflict :-( _unresolvedAttributesConflict.add(conflict); } } } } for (String attributeName : _diff2.getAddedAttributes().keySet()) { if (_diff1.getAddedAttributes().get(attributeName) == null) { // here we have test if the added attribute isn't in a deleted // part of the tree if (elementIsInOneOfThoseTree(_diff2.getSourceElement(), _diff1.getDocumentMapping().getRemovedElements())) { // here we have a conflict : the modified attribute is in a // part of the tree that has been deleted UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, null, _diff2 .getAddedAttributes().get(attributeName), _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheAdditionDeleteConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the // conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually solve // the conflict :-( _unresolvedAttributesConflict.add(conflict); } } else { // non conflicting addition _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.INSERT, attributeName, _diff2 .getAddedAttributes().get(attributeName).getValue(), _mergedElement)); } } else { // we have already seen this conflicting case in the previous // loop } } } public static boolean elementIsInOneOfThoseTree(Element sourceElement, List<Content> removedElements) { Element e = sourceElement; if (removedElements.contains(e)) { return true; } if (e.getParentElement() == null) { return false; } return elementIsInOneOfThoseTree(e.getParentElement(), removedElements); } public static Element getCauseOfConflict(Element sourceElement, Vector<Content> removedElements) { Element e = sourceElement; if (removedElements.contains(e)) { return e; } if (e.getParentElement() == null) { return null; } return getCauseOfConflict(e.getParentElement(), removedElements); } private void processToUpdateOfAttributes() { String attributeName = null; for (Entry<Attribute, Attribute> e : _diff1.getUpdatedAttributes().entrySet()) { attributeName = e.getKey().getName(); if (_diff2.getUpdatedAttributes().get(e.getKey()) == null && _diff2.getDeletedAttributes().get(attributeName) == null) { // here we have test if the added attribute isn't in a deleted // part of the tree if (elementIsInOneOfThoseTree(_diff1.getSourceElement(), _diff2.getDocumentMapping().getRemovedElements())) { // here we have a conflict : the modified attribute is in a // part of the tree that has been deleted UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, e.getValue(), null, _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheUpdateDeleteTreeConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the // conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually solve // the conflict :-( _unresolvedAttributesConflict.add(conflict); } } else {// non conflicting update _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.UPDATE, attributeName, e.getValue().getValue(), _mergedElement)); } } else { if (_diff2.getUpdatedAttributes().get(e.getKey()) != null) { // the same attribute was updated on both sides if (_diff2.getUpdatedAttributes().get(e.getKey()).getValue() .equals(_diff1.getUpdatedAttributes().get(e.getKey()).getValue())) { // ouf : the same value has been set on both side _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.UPDATE, attributeName, e.getValue() .getValue(), _mergedElement)); } else { // !!! WE HAVE A CONFLICT !!! // same attribute updated on both side with different // values !!! UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, e.getValue(), _diff2.getUpdatedAttributes().get(e.getKey()), _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheUpdateConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually // solve the conflict :-( _unresolvedAttributesConflict.add(conflict); } } } else { // the attribute has been updated in diff1 and deleted in diff2 UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, e.getValue(), null, _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheUpdateDeleteConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually // solve the conflict :-( _unresolvedAttributesConflict.add(conflict); } } } } for (Entry<Attribute, Attribute> e : _diff2.getUpdatedAttributes().entrySet()) { attributeName = e.getKey().getName(); if (_diff1.getUpdatedAttributes().get(e.getKey()) == null && _diff1.getDeletedAttributes().get(attributeName) == null) { // here we have test if the added attribute isn't in a deleted // part of the tree if (elementIsInOneOfThoseTree(_diff2.getSourceElement(), _diff1.getDocumentMapping().getRemovedElements())) { // here we have a conflict : the modified attribute is in a // part of the tree that has been deleted UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, null, e.getValue(), _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheUpdateDeleteTreeConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the // conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually solve // the conflict :-( _unresolvedAttributesConflict.add(conflict); } } else {// non conflicting update _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.UPDATE, attributeName, e.getValue().getValue(), _mergedElement)); } } else { if (_diff1.getUpdatedAttributes().get(e.getKey()) != null) { // this case was solved in the previous loop } else { // the attribute has been updated in diff2 and deleted in diff1 UnresolvedAttributesConflict conflict = new UnresolvedAttributesConflict(_merge, _sourceElement, null, e.getValue(), _mergedElement); MergeAttributeAction autoResolvedConflictAction = tryAutoResolvingTheUpdateDeleteConflict(conflict); if (autoResolvedConflictAction != null) { // Whooah : this tool is smart enough to solve the conflict _autoResolvedAttributesConflict.put(conflict, autoResolvedConflictAction); conflict.setSolveAction(autoResolvedConflictAction, false); } else { // I'm afraid that the user will have to manually // solve the conflict :-( _unresolvedAttributesConflict.add(conflict); } } } } } private void processToDeletionOfAttributes() { for (Entry<String, Attribute> e : _diff1.getDeletedAttributes().entrySet()) { if (_diff2.getUpdatedAttributes().get(e.getValue()) == null) { _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.DELETE, e.getKey(), null, _mergedElement)); } else { // this case has been solved in method : processToUpdateOfAttributes } } for (Entry<String, Attribute> e : _diff2.getDeletedAttributes().entrySet()) { if (_diff1.getUpdatedAttributes().get(e.getValue()) == null) { _mergeAttributesActions.add(new MergeAttributeAction(0, MergeActionType.DELETE, e.getKey(), null, _mergedElement)); } else { // this case has been solved in method : processToUpdateOfAttributes } } } private MergeAttributeAction tryAutoResolvingTheUpdateDeleteConflict(UnresolvedAttributesConflict conflict) { // TODO Auto-generated method stub return null; } private MergeAttributeAction tryAutoResolvingTheAdditionConflict(UnresolvedAttributesConflict conflict) { // TODO Auto-generated method stub return null; } private MergeAttributeAction tryAutoResolvingTheUpdateConflict(UnresolvedAttributesConflict conflict) { MergeAttributeRule rule = null; rule = new NewestDate(conflict); if (rule.canBeApplyed()) { return rule.getAction(); } rule = new OldestDate(conflict); if (rule.canBeApplyed()) { return rule.getAction(); } rule = new AveragePosition(conflict); if (rule.canBeApplyed()) { return rule.getAction(); } rule = new AverageLocation(conflict); if (rule.canBeApplyed()) { return rule.getAction(); } rule = new MaxValue(conflict); if (rule.canBeApplyed()) { return rule.getAction(); } return null; } private MergeAttributeAction tryAutoResolvingTheAdditionDeleteConflict(UnresolvedAttributesConflict conflict) { // TODO Auto-generated method stub return null; } private MergeAttributeAction tryAutoResolvingTheUpdateDeleteTreeConflict(UnresolvedAttributesConflict conflict) { // TODO Auto-generated method stub return null; } }