/*
* (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.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Vector;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Parent;
import org.jdom2.Text;
import org.jdom2.output.Format;
import org.jdom2.output.LineSeparator;
import org.jdom2.output.XMLOutputter;
import org.openflexo.diff.DiffSource;
import org.openflexo.diff.merge.IMerge;
import org.openflexo.diff.merge.MergeChange;
import org.openflexo.diff.merge.MergeChange.ChangeCategory;
import org.openflexo.diff.merge.MergedDocumentType;
import org.openflexo.xml.diff2.DocumentsMapping;
import org.openflexo.xml.diff2.DocumentsMapping.ParentReferences;
import org.openflexo.xml.diff3.mergerule.MergeTextRule;
import org.openflexo.xml.diff3.mergerule.ResourceCount;
import org.openflexo.xmlcode.ModelEntity;
import org.openflexo.xmlcode.ModelProperty;
import org.openflexo.xmlcode.XMLMapping;
public class XMLDiff3 extends Observable implements IMerge {
private Document _src;
private Document _target1;
private Document _target2;
private DocumentsMapping _mapping1;
private DocumentsMapping _mapping2;
private Vector<UnresolvedConflict> _unresolvedConflict;
private Document mergedDocument;
private Vector<MergeElementAction> _mergeElementsAction;
private Hashtable<UnresolvedConflict, MergeElementAction> _autoMergeElementsAction;
private Hashtable<UnresolvedConflict, MergeTextAction> _autoMergeTextsAction;
private Hashtable<UnresolvedConflict, MergeAttributeAction> _autoMergeAttributesAction;
private Vector<MergeTextAction> _mergeTextsActions;
private Vector<MergeMoveAction> _mergeMovesActions;
private Hashtable<Content, Content> _srcMergedMapping;
private Hashtable<IndexedContent, Content> _pendingInsertions;
private Hashtable<UnresolvedConflict, MergeAction> _manualChoices;
private XMLMapping _mapping;
private int _conflictIndex;
// compliance with other merge algorithm
private DiffSource _leftSource;
private DiffSource _rightSource;
private DiffSource _originalSource;
private DiffSource _mergedSource;
// private MergedDocumentType _docType;
public static final boolean DEBUG = false;
private class IndexedContent {
private Content _content;
private Integer _index;
public IndexedContent(Content content, Integer index) {
super();
_content = content;
_index = index;
}
public Content getContent() {
return _content;
}
public Integer getIndex() {
return _index;
}
}
public XMLDiff3(Document src, Document target1, Document target2, XMLMapping mapping, MergedDocumentType mergedDocumentType) {
super();
_unresolvedConflict = new Vector<UnresolvedConflict>();
_src = src;
_target1 = target1;
_target2 = target2;
// _docType = mergedDocumentType;
_conflictIndex = 0;
_mapping1 = new DocumentsMapping(_src, _target1);
_mapping2 = new DocumentsMapping(_src, _target2);
_leftSource = new DiffSource(getXMLText(target2));
_rightSource = new DiffSource(getXMLText(target1));
_originalSource = new DiffSource(getXMLText(src));
_mapping = mapping;
_mergeElementsAction = new Vector<MergeElementAction>();
_mergeTextsActions = new Vector<MergeTextAction>();
_srcMergedMapping = new Hashtable<Content, Content>();
_pendingInsertions = new Hashtable<IndexedContent, Content>();
_autoMergeElementsAction = new Hashtable<UnresolvedConflict, MergeElementAction>();
_autoMergeTextsAction = new Hashtable<UnresolvedConflict, MergeTextAction>();
_autoMergeAttributesAction = new Hashtable<UnresolvedConflict, MergeAttributeAction>();
_manualChoices = new Hashtable<UnresolvedConflict, MergeAction>();
mergedDocument = new Document();
// now let's parse the source Document
parse(_src.getRootElement(), mergedDocument);
// everything is parsed and all required actions are in actions container.
// let's apply all those actions now
// Note that all merage attribute action (normal or automatic merge) are already executed !!!
// let's sort actions
MergeAction[] _actions = new MergeAction[_mergeElementsAction.size() + _mergeTextsActions.size() + _autoMergeElementsAction.size()
+ _autoMergeTextsAction.size()];
int i = 0;
Enumeration en = _mergeElementsAction.elements();
while (en.hasMoreElements()) {
_actions[i] = (MergeAction) en.nextElement();
i++;
}
en = _mergeTextsActions.elements();
while (en.hasMoreElements()) {
_actions[i] = (MergeAction) en.nextElement();
i++;
}
en = _autoMergeElementsAction.elements();
while (en.hasMoreElements()) {
_actions[i] = (MergeAction) en.nextElement();
i++;
}
en = _autoMergeTextsAction.elements();
while (en.hasMoreElements()) {
_actions[i] = (MergeAction) en.nextElement();
i++;
}
Arrays.sort(_actions, new MergeActionComparator());
// now we execute everything
for (int j = 0; j < _actions.length; j++) {
_actions[j].execute();
}
if (DEBUG) {
System.out.println("pending insertion count = " + _pendingInsertions.size());
if (_unresolvedConflict.size() > 0) {
System.out.println("Unresoved conflicts :\n");
Enumeration<UnresolvedConflict> en2 = _unresolvedConflict.elements();
while (en2.hasMoreElements()) {
System.out.println(en2.nextElement());
}
} else {
System.out.println("All conflict are resolved");
}
System.out.println(getAutoMergeResolutionCount() + " conflicts were resolved automatically.");
}
}
public int getAutoMergeResolutionCount() {
return _autoMergeElementsAction.size() + _autoMergeTextsAction.size() + _autoMergeAttributesAction.size();
}
private class MergeActionComparator implements Comparator<MergeAction> {
@Override
public int compare(MergeAction arg0, MergeAction arg1) {
return arg0.getActionIndex() - arg1.getActionIndex();
}
}
public void registerManualChoice(UnresolvedConflict conflict, MergeAction action) {
if (!_unresolvedConflict.contains(conflict)) {
throw new IllegalStateException("Cannot resolve a conflict manually if this conflict doesn't exists");
}
_manualChoices.put(conflict, action);
}
public void unregisterManualChoice(UnresolvedConflict conflict) {
if (_manualChoices.get(conflict) == null) {
throw new IllegalStateException("Cannot remove a manual choice if this conflict doesn't exists");
}
_manualChoices.remove(conflict);
}
public boolean allConflictsAreResolved() {
return _unresolvedConflict.size() == 0;
}
public boolean allConflictsAreManuallyResolved() {
Enumeration<UnresolvedConflict> en = _unresolvedConflict.elements();
while (en.hasMoreElements()) {
if (!en.nextElement().isSolved()) {
return false;
}
}
return true;
}
public int getConflictManuallyResolvedCount() {
int reply = 0;
Enumeration<UnresolvedConflict> en = _unresolvedConflict.elements();
while (en.hasMoreElements()) {
if (en.nextElement().isSolved()) {
reply++;
}
}
return reply;
}
// public Document applyAllManualChoices(){
// Enumeration<UnresolvedConflict> en = _manualChoices.keys();
// while(en.hasMoreElements()){
// MergeAction action = _manualChoices.get(en.nextElement());
// action.execute();
// }
// return getMergedDocument();
// }
private MergeElementAction tryAutoResolvingTheAdditionDeleteConflict(Element parentSource, Content insertedElement) {
// TODO Auto-generated method stub
return null;
}
public Document getMergedDocument() {
return mergedDocument;
}
public Content getMergedContentMatchingContentInTargetOfMapping(Content contentInTargetOfMapping, DocumentsMapping docMapping) {
return DocumentsMapping.getContentMatchingContent(contentInTargetOfMapping, docMapping.getTargetDocument(), getMergedDocument());
}
private void parse(Content srcContent, Parent parent) {
boolean hasMoved = false;
boolean isMoveConflict = false;
Element parentInMergedDocument = null;
Element newParentInTargetOfMapping = null;
int moveInsertionIndex = -1;
if (srcContent instanceof Element) {
// before everything... srcContent has moved ?
DocumentsMapping.ParentReferences move1 = _mapping1.getMovedElements().get(srcContent);
DocumentsMapping.ParentReferences move2 = _mapping2.getMovedElements().get(srcContent);
if (move1 != null || move2 != null) {
hasMoved = true; // position this flag and use it at insertion time.
if (move2 == null) { // move in branch1
newParentInTargetOfMapping = move1.getParentInTarget();
parentInMergedDocument = (Element) getMergedContentMatchingContentInTargetOfMapping(newParentInTargetOfMapping,
_mapping1);
moveInsertionIndex = DocumentsMapping.indexOfElement(move1.getContentInTarget());
} else if (move1 == null) { // move in branch2
newParentInTargetOfMapping = move2.getParentInTarget();
parentInMergedDocument = (Element) getMergedContentMatchingContentInTargetOfMapping(newParentInTargetOfMapping,
_mapping2);
moveInsertionIndex = DocumentsMapping.indexOfElement(move2.getContentInTarget());
} else if (isSameMove(move1, move2)) { // same move in both branch
newParentInTargetOfMapping = move1.getParentInTarget();
parentInMergedDocument = (Element) getMergedContentMatchingContentInTargetOfMapping(newParentInTargetOfMapping,
_mapping1);
moveInsertionIndex = DocumentsMapping.indexOfElement(move1.getContentInTarget());
} else { // complex case : different move in both branch
isMoveConflict = true;
Element newParentInTargetOfMapping1 = move1.getParentInTarget();
Element parent1InMergedDocument = (Element) getMergedContentMatchingContentInTargetOfMapping(
newParentInTargetOfMapping1, _mapping1);
Element newParentInTargetOfMapping2 = move2.getParentInTarget();
Element parent2InMergedDocument = (Element) getMergedContentMatchingContentInTargetOfMapping(
newParentInTargetOfMapping2, _mapping2);
int insertionIndex1 = DocumentsMapping.indexOfElement(move1.getContentInTarget());
int insertionIndex2 = DocumentsMapping.indexOfElement(move2.getContentInTarget());
UnresolvedMoveConflict conflict = new UnresolvedMoveConflict(this, _conflictIndex++, (Element) srcContent,
parent1InMergedDocument, parent2InMergedDocument, insertionIndex1, insertionIndex2);
MergeMoveAction autoResolvedConflictAction = tryAutoResolvingMoveConflict(conflict);
if (autoResolvedConflictAction != null) {
// Whooah : this tool is smart enough to solve the conflict
_mergeMovesActions.add(autoResolvedConflictAction);
} else {
// I'm afraid that the user will have to manually
// solve the conflict :-(
_unresolvedConflict.add(conflict);
}
}
}
if (parentInMergedDocument != null) {
if (parentInMergedDocument.getDocument().equals(getMergedDocument())) {
System.err.println("OK parent in merged doc is really in merge doc");
} else {
System.err.println("????????????????????? KO KO KO");
}
}
// at this point we assume that the job required for move action is done
// retreive comparaison data (note that if they exists : mapping are of type : DocumentsMapping.MatchingElements)
DocumentsMapping.MatchingElements matching1 = (DocumentsMapping.MatchingElements) _mapping1
.getMatchingXMLForSourceContent(srcContent);
DocumentsMapping.MatchingElements matching2 = (DocumentsMapping.MatchingElements) _mapping2
.getMatchingXMLForSourceContent(srcContent);
// case 1 : Element is still in both branch
if (matching1 != null & matching2 != null) {
// we are in the most common case : srcContent is still alive in both branch
Element builtElement = (Element) _srcMergedMapping.get(srcContent);
if (builtElement == null) {
builtElement = new Element(((Element) srcContent).getName());
}
_srcMergedMapping.put(srcContent, builtElement);
AttributesChange attributesChange = new AttributesChange(this, matching1.getAttributesDiff(),
matching2.getAttributesDiff(), (Element) srcContent, builtElement);
// this call is very important since it will fill buildedElement with all it's content
attributesChange.getMergedElement();
// if we have unresolved conflicts on Attributes, then let's put them into the Diff3 unresolved list
_unresolvedConflict.addAll(attributesChange.getUnresolvedConflicts());
_autoMergeAttributesAction.putAll(attributesChange.getAutoResolvedConflicts());
// now : let's insert the builded element into it's parent
if (parent instanceof Document) {
// we are sure that the root Element didn't move : so we don't care about the hasMoved flag
((Document) parent).setRootElement(builtElement);
} else {
if (hasMoved) {
if (parentInMergedDocument != null) {
// the new parent exist, so we are able to do the insertion immediately
System.err.println("insert a moved element : " + builtElement + "(id=" + builtElement.getAttributeValue("id")
+ ")" + " into " + parentInMergedDocument + "(id=" + parentInMergedDocument.getAttributeValue("id")
+ ")");
insert(parentInMergedDocument, builtElement, moveInsertionIndex);
} else {
// the new parent doesn't exist yet, so we put the new Element in a queue
System.err.println("queue a moved element : " + builtElement);
_pendingInsertions.put(new IndexedContent(builtElement, moveInsertionIndex), newParentInTargetOfMapping);
}
} else {
insert((Element) parent, builtElement, -1);
}
}
// At this point the buildedElement is either in the merged document or in the pending insertions table
// now let's look at the pending insertions to see if one of them need to be inserted here.
Hashtable<Content, Integer> contentsToInsert = new Hashtable<Content, Integer>();
// first we need to retreive all content to insert
Enumeration<IndexedContent> keyEnum = _pendingInsertions.keys();
IndexedContent contentToInsert = null;
while (keyEnum.hasMoreElements()) {
contentToInsert = keyEnum.nextElement();
Content parentInTarget = _pendingInsertions.get(contentToInsert);
if (parentInTarget.equals(matching1.getTargetElement()) || parentInTarget.equals(matching2.getTargetElement())) {
contentsToInsert.put(contentToInsert.getContent(), contentToInsert.getIndex());
}
}
Enumeration<Content> en0 = contentsToInsert.keys();
Content contentToInsertAsAChildOfBuildedElement = null;
while (en0.hasMoreElements()) {
contentToInsertAsAChildOfBuildedElement = en0.nextElement();
insert(builtElement, contentToInsertAsAChildOfBuildedElement,
contentsToInsert.get(contentToInsertAsAChildOfBuildedElement));
// remove the pending insertion
_pendingInsertions.remove(contentToInsertAsAChildOfBuildedElement);
}
// finally : recursive call to the embedded items.
Iterator<Content> it = ((Element) srcContent).getContent().iterator();
Content child = null;
while (it.hasNext()) {
child = it.next();
if (child instanceof Text && ((Text) child).getTextTrim().length() == 0) {
continue;
}
if (_srcMergedMapping.get(child) == null) {
parse(child, builtElement);
}
}
// now let's take a look at Element added in _mapping1 to see if one of them must be inserted here
Map<Content, Integer> additions1 = _mapping1.getAddedElementsInSourceRef((Element) srcContent);
for (Entry<Content, Integer> e : additions1.entrySet()) {
insert(builtElement, e.getKey(), e.getValue());
}
// now let's take a look at Element added in _mapping2 to see if one of them must be inserted here
Map<Content, Integer> additions2 = _mapping2.getAddedElementsInSourceRef((Element) srcContent);
for (Entry<Content, Integer> e : additions2.entrySet()) {
insert(builtElement, e.getKey(), e.getValue());
}
// before recursive iteration, let's see if there is some new child
// that were added in branch1 or branch2
} else if (matching1 == null && matching2 != null) {
// removed from branch1, but still in branch2
// let's see if it has been modified in branch2
if (_mapping2.containsUpdateOrModificationsUnder(srcContent)) {
// we have a conflict... :-(
UnresolvedDeleteConflict conflict = new UnresolvedDeleteConflict(this, _conflictIndex++, (Element) srcContent,
(Element) parent, null, matching2.getTargetElement(), -1, DocumentsMapping.indexOfElement(matching2
.getTargetElement()));
_unresolvedConflict.add(conflict);
} else {
// no conflict : the removal of srcContent done in branch1 is "commited"
}
} else if (matching1 != null && matching2 == null) {
// removed from branch1, but still in branch1
// let's see if it has been modified in branch1
if (_mapping1.containsUpdateOrModificationsUnder(srcContent)) {
// we have a conflict... :-(
UnresolvedDeleteConflict conflict = new UnresolvedDeleteConflict(this, _conflictIndex++, (Element) srcContent,
(Element) parent, matching1.getTargetElement(), null, DocumentsMapping.indexOfElement(matching1
.getTargetElement()), -1);
_unresolvedConflict.add(conflict);
} else {
// no conflict : the removal of srcContent done in branch2 is "commited"
}
} else if (matching1 == null && matching2 == null) {
// nothing match the current srcContent in none of the 2 branches
// so : it seems that that it has been removed on both side
// so : no need to go any further
}
} else { // srcContent is a Text
// just like with Element : we have 4 different cases
DocumentsMapping.MatchingTexts matching1 = (DocumentsMapping.MatchingTexts) _mapping1
.getMatchingXMLForSourceContent(srcContent);
DocumentsMapping.MatchingTexts matching2 = (DocumentsMapping.MatchingTexts) _mapping2
.getMatchingXMLForSourceContent(srcContent);
if (matching1 != null && matching2 != null) {
if (matching1.isUnchanged()) {
// so we can get matching2
Text buildedText = new Text(matching2.getTargetText().getText());
_srcMergedMapping.put(srcContent, buildedText);
// ((Element)parent).addContent(buildedText); // note that we assume that "parent" is not the document
_mergeTextsActions.add(new MergeTextAction(_conflictIndex++, MergeActionType.CHOOSE1, (Element) parent, buildedText,
null));
} else if (matching2.isUnchanged()) {
// so we can get matching1
Text buildedText = new Text(matching1.getTargetText().getText());
_srcMergedMapping.put(srcContent, buildedText);
// ((Element)parent).addContent(buildedText); // note that we assume that "parent" is not the document
_mergeTextsActions.add(new MergeTextAction(_conflictIndex++, MergeActionType.CHOOSE1, (Element) parent, buildedText,
null));
} else {
// change on both side ==> conflict
UnresolvedTextConflict conflict = new UnresolvedTextConflict(this, _conflictIndex++, (Element) parent,
matching1.getTargetText(), matching2.getTargetText());
MergeTextAction autoResolvedConflictAction = tryAutoResolvingTextUpdateConflict(conflict);
if (autoResolvedConflictAction != null) {
// Whooah : this tool is smart enough to solve the conflict
_mergeTextsActions.add(autoResolvedConflictAction);
} else {
// I'm afraid that the user will have to manually
// solve the conflict :-(
_unresolvedConflict.add(conflict);
}
}
} else if (matching1 == null && matching2 != null) {
if (matching2.isUnchanged()) {
// no change in 2, removed in 1 ==> removed
} else {
// changed in 2, removed in 1 ==> conflict
_unresolvedConflict.add(new UnresolvedTextConflict(this, _conflictIndex++, (Element) parent, null, matching2
.getTargetText()));
}
} else if (matching1 != null && matching2 == null) {
if (matching1.isUnchanged()) {
// no change in 1, removed in 2 ==> removed
} else {
// changed in 1, removed in 2 ==> conflict
_unresolvedConflict.add(new UnresolvedTextConflict(this, _conflictIndex++, (Element) parent, matching1.getTargetText(),
null));
}
} else if (matching1 == null && matching2 == null) {
// nothing match the current srcContent in none of the 2 branches
// so : it seems that that it has been removed on both side
// so : no need to go any further
}
}
}
private MergeMoveAction tryAutoResolvingMoveConflict(UnresolvedMoveConflict conflict) {
// TODO Auto-generated method stub
return null;
}
private MergeTextAction tryAutoResolvingTextUpdateConflict(UnresolvedTextConflict conflict) {
MergeTextRule rule = null;
rule = new ResourceCount(conflict);
if (rule.canBeApplyed()) {
return rule.getAction();
}
return null;
}
private boolean isSameMove(ParentReferences move1, ParentReferences move2) {
if (move1.getParentInSource().equals(move2.getParentInSource())) {
Content destinationOfMove1MappedIntoDoc2 = DocumentsMapping.getContentMatchingContent(move1.getParentInTarget(),
_mapping1.getTargetDocument(), _mapping2.getTargetDocument());
if (destinationOfMove1MappedIntoDoc2 != null) {
return destinationOfMove1MappedIntoDoc2.equals(move2.getParentInTarget());
}
}
return false;
}
private void insert(Element parent, Content child, Integer index) {
Content conflictingItem = canAcceptChild(parent, child);
if (conflictingItem == null) {
// parent.addContent(child);
MergeElementAction mergeAction = new MergeElementAction(_conflictIndex++, MergeActionType.INSERT, child, parent, index);
_mergeElementsAction.add(mergeAction);
if (child instanceof Element) {
registerPendingAddition(parent, (Element) child);
}
} else {
if (conflictingItem instanceof Element) {
UnresolvedInsertionConflict conflict = new UnresolvedInsertionConflict(this, _conflictIndex++, parent,
(Element) conflictingItem, (Element) child);
MergeElementAction autoResolvedConflictAction = tryAutoResolvingAdditionConflict(conflict);
if (autoResolvedConflictAction != null) {
// Whooah : this tool is smart enough to solve the conflict
// _mergeElementsAction.add(autoResolvedConflictAction);
_autoMergeElementsAction.put(conflict, autoResolvedConflictAction);
conflict.setSolveAction(autoResolvedConflictAction, false);
} else {
// I'm afraid that the user will have to manually
// solve the conflict :-(
_unresolvedConflict.add(conflict);
}
} else {
UnresolvedTextConflict conflict = new UnresolvedTextConflict(this, _conflictIndex++, parent, (Text) conflictingItem,
(Text) child);
MergeTextAction autoResolvedConflictAction = tryAutoResolvingTextUpdateConflict(conflict);
if (autoResolvedConflictAction != null) {
// Whooah : this tool is smart enough to solve the conflict
// _mergeTextsActions.add(autoResolvedConflictAction);
_autoMergeTextsAction.put(conflict, autoResolvedConflictAction);
conflict.setSolveAction(autoResolvedConflictAction, false);
} else {
// I'm afraid that the user will have to manually
// solve the conflict :-(
_unresolvedConflict.add(conflict);
}
}
}
}
private MergeElementAction tryAutoResolvingAdditionConflict(UnresolvedInsertionConflict conflict) {
// TODO Auto-generated method stub
return null;
}
private Content canAcceptChild(Element parent, Content child) {
if (child instanceof Element) {
if (_mapping != null) {
ModelEntity parentEntity = _mapping.entityWithXMLTag(parent.getName());
if (parentEntity == null) {
// System.out.println("Cannot fing entity for "+parent.getName()+" in mapping "+_mapping.toString());
return null;
}
Hashtable<ModelProperty, Vector<String>> potentialMatches = parentEntity.getAllNonAttributeProperties();
Enumeration<ModelProperty> en = potentialMatches.keys();
ModelProperty prop = null;
while (en.hasMoreElements()) {
prop = en.nextElement();
Vector<String> tags = potentialMatches.get(prop);
if (tags.contains(((Element) child).getName())) {
if (!prop.isSingle()) {
return null;
}
Iterator<String> it = tags.iterator();
String tag = null;
while (it.hasNext()) {
tag = it.next();
Content concurrent = findAPendingAdditionWithTag(parent, tag);
if (concurrent != null) {
return concurrent;
}
}
// return null;
}
}
ModelProperty childProperty = parentEntity.getModelPropertyWithXMLTag(((Element) child).getName());
if (childProperty.isSingle()) {
return parent.getChild(((Element) child).getName());
}
}
} else {
Iterator<Element> it = parent.getChildren().iterator();
Content c = null;
while (it.hasNext()) {
c = it.next();
if (c instanceof Text && ((Text) c).getTextTrim().length() > 0) {
return c;
}
}
}
return null;
}
private Hashtable<Element, Vector<Element>> _pendingAdditions;
private void registerPendingAddition(Element parent, Element child) {
if (_pendingAdditions == null) {
_pendingAdditions = new Hashtable<Element, Vector<Element>>();
}
Vector<Element> inserted = _pendingAdditions.get(parent);
if (inserted == null) {
inserted = new Vector<Element>();
_pendingAdditions.put(parent, inserted);
}
inserted.add(child);
}
private Content findAPendingAdditionWithTag(Element parent, String tag) {
if (_pendingAdditions == null) {
_pendingAdditions = new Hashtable<Element, Vector<Element>>();
}
Vector<Element> additions = _pendingAdditions.get(parent);
if (additions != null) {
Enumeration<Element> en = additions.elements();
while (en.hasMoreElements()) {
Element item = en.nextElement();
if (tag.equals(item.getName())) {
return item;
}
}
}
return null;
}
public Vector<UnresolvedConflict> getAllUnresolvedConflicts() {
return _unresolvedConflict;
}
@Override
public int getConflictsChangeCount() {
return getAllUnresolvedConflicts().size() + getAutoMergeResolutionCount();
}
@Override
public int getLeftChangeCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getMergedText() {
// applyAllManualChoices();
return getXMLText(getMergedDocument());
}
private String getXMLText(Document doc) {
StringWriter writer = new StringWriter();
Format prettyFormat = Format.getPrettyFormat();
prettyFormat.setLineSeparator(LineSeparator.SYSTEM);
XMLOutputter outputter = new XMLOutputter(prettyFormat);
try {
outputter.output(doc, writer);
} catch (IOException e) {
e.printStackTrace();
}
return writer.toString();
}
public static String getXMLText(Element el) {
StringWriter writer = new StringWriter();
Format prettyFormat = Format.getPrettyFormat();
prettyFormat.setLineSeparator(LineSeparator.SYSTEM);
XMLOutputter outputter = new XMLOutputter(prettyFormat);
try {
outputter.output(el, writer);
} catch (IOException e) {
e.printStackTrace();
}
return writer.toString();
}
@Override
public int getResolvedConflictsChangeCount() {
return getAutoMergeResolutionCount() + getConflictManuallyResolvedCount();
}
@Override
public int getRightChangeCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean isResolved() {
return allConflictsAreManuallyResolved();
}
@Override
public MergeChange changeBefore(MergeChange change) {
// TODO Auto-generated method stub
return null;
}
@Override
public Vector<MergeChange> filteredChangeList(List<ChangeCategory> selectedCategories) {
// TODO Auto-generated method stub
return new Vector<MergeChange>();
}
@Override
public Vector<MergeChange> getChanges() {
// TODO Auto-generated method stub
return new Vector<MergeChange>();
}
@Override
public DiffSource getLeftSource() {
return _leftSource;
}
@Override
public DiffSource getMergedSource() {
if (_mergedSource == null) {
_mergedSource = new DiffSource(getMergedText());
}
return new DiffSource(getMergedText());
}
@Override
public DiffSource getOriginalSource() {
return _originalSource;
}
@Override
public DiffSource getRightSource() {
return _rightSource;
}
public void propagateChange() {
setChanged();
notifyObservers();
}
}