/*
* (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.diff2;
import java.util.Collection;
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.Vector;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Text;
import org.jdom2.filter.ElementFilter;
import org.openflexo.xml.diff3.XMLDiff3;
public class DocumentsMapping {
private Document _src;
private Document _target;
private Map<Content, Content> _srcTarget;
private Map<Content, Content> _targetSource;
private Map<Content, ParentReferences> _movedElements;
private Map<Content, MatchingXML> _matchingXMLs;
private List<Content> _removedFromTarget;
private List<Content> _addedInTarget;
private Map<Content, ModifierFlags> _modifiersFlags;
public DocumentsMapping(Document src, Document target) {
super();
_src = src;
_target = target;
_srcTarget = new Hashtable<Content, Content>();
_targetSource = new Hashtable<Content, Content>();
_removedFromTarget = new Vector<Content>();
_addedInTarget = new Vector<Content>();
_movedElements = new Hashtable<Content, ParentReferences>();
_matchingXMLs = new Hashtable<Content, MatchingXML>();
_modifiersFlags = new Hashtable<Content, ModifierFlags>();
// first pass over src
_srcTarget.put(_src.getRootElement(), _target.getRootElement());
_matchingXMLs.put(_src.getRootElement(), new MatchingElements(_src.getRootElement(), _target.getRootElement()));
processToMappingSrcTarget(_src.getRootElement());
// first pass over target
_targetSource.put(_target.getRootElement(), _src.getRootElement());
processToMappingTargetSrc(_target.getRootElement());
checkMapping();
}
public Document getTargetDocument() {
return _target;
}
public Document getSourceDocument() {
return _src;
}
public List<Content> getRemovedElements() {
return _removedFromTarget;
}
public List<Content> getAddedElements() {
return _addedInTarget;
}
public Map<Content, Integer> getAddedElementsInSourceRef(Element srcRef) {
Map<Content, Integer> reply = new Hashtable<Content, Integer>();
Element parentOfItemInTarget;
for (Content item : _addedInTarget) {
parentOfItemInTarget = item.getParentElement();
Element parentOfItemInSrc = (Element) _targetSource.get(parentOfItemInTarget);
if (srcRef.equals(parentOfItemInSrc)) {
reply.put(item.clone(), Integer.valueOf(indexOfElement(item)));
}
}
return reply;
}
public static int indexOfElement(Content element) {
int reply = 0;
Iterator<Content> it = element.getParentElement().getContent().iterator();
while (it.hasNext()) {
if (it.next().equals(element)) {
return reply;
}
reply++;
}
return reply;
}
public Map<Content, ParentReferences> getMovedElements() {
return _movedElements;
}
public MatchingXML getMatchingXMLForSourceContent(Content content) {
return _matchingXMLs.get(content);
}
private void checkMapping() {
if (_srcTarget.size() != _targetSource.size()) {
System.err.println("Mapping aren't of the same size : srcTarget.size=" + _srcTarget.size() + " targetSource.size="
+ _targetSource.size());
} else {
// System.out.println("Found mapping for : "+_targetSource.size()+" xml objects");
}
for (Entry<Content, Content> e : _srcTarget.entrySet()) {
Content itemKey = e.getKey();
Content matchingTargetInSrcTarget = e.getValue();
Content matchingSourceInTargetSource = _targetSource.get(matchingTargetInSrcTarget);
if (matchingSourceInTargetSource == null) {
System.err.println("Cannot find the pending of " + itemKey + "," + matchingTargetInSrcTarget + " into targetSource");
} else {
if (!matchingSourceInTargetSource.equals(itemKey)) {
System.err.println("mapping does'nt match :" + itemKey + "," + matchingTargetInSrcTarget + ","
+ matchingSourceInTargetSource);
}
}
}
if (XMLDiff3.DEBUG) {
System.out.println("xmlObjects removed from target : " + _removedFromTarget.size());
print(_removedFromTarget);
System.out.println("xmlObjects added in target : " + _addedInTarget.size());
print(_addedInTarget);
System.out.println("xmlObjects moved in target : " + _movedElements.size());
print(_movedElements.keySet());
}
}
private void print(Collection<Content> v) {
for (Content content : v) {
// System.out.println(content);
}
}
private void print(Hashtable<Content, ParentReferences> h) {
Enumeration<Content> en = h.keys();
Content item = null;
while (en.hasMoreElements()) {
item = en.nextElement();
System.out.println("\t" + item + " " + print(item));
System.out.println(h.get(item));
System.out.println("------");
}
}
private String print(Content c) {
if (c instanceof Element) {
return c + " id=" + ((Element) c).getAttributeValue("id");
}
if (c instanceof Text) {
return c + " text=" + ((Text) c).getText();
}
return c + " unknown !!!";
}
private void processToMappingSrcTarget(Element e) {
for (int i = 0; i < e.getContentSize(); i++) {
Content content = e.getContent(i);
if (content instanceof Text) {
if (((Text) content).getText().trim().length() == 0) {
continue;
}
}
Content matchingContent = getTargetContentForSource(content);
if (matchingContent == null) {
_removedFromTarget.add(content);
registerModifier(content, false, true, false, false, false, false);
if (content instanceof Element) {
processToMappingSrcTarget((Element) content);
}
} else {
_srcTarget.put(content, matchingContent);
if (content instanceof Element) {
MatchingElements match = new MatchingElements((Element) content, (Element) matchingContent);
if (!match.isUnchanged()) {
registerModifier(content, false, false, true, false, false, false);
}
_matchingXMLs.put(content, match);
} else if (content instanceof Text) {
MatchingTexts match = new MatchingTexts((Text) content, (Text) matchingContent);
if (!match.isUnchanged()) {
registerModifier(content, false, false, false, false, false, true);
}
_matchingXMLs.put(content, match);
}
if (!content.getParentElement().equals(getSourceContentForTarget(matchingContent.getParentElement()))) {
// element has moved
ParentReferences parentRefs = new ParentReferences(content.getParentElement(), matchingContent.getParentElement(),
matchingContent);
_movedElements.put(content, parentRefs);
registerModifier(content, false, false, false, false, true, false);
}
if (content instanceof Element) {
processToMappingSrcTarget((Element) content);
}
}
}
}
private void processToMappingTargetSrc(Element e) {
for (int i = 0; i < e.getContentSize(); i++) {
Content content = e.getContent(i);
if (content instanceof Text) {
if (((Text) content).getText().trim().length() == 0) {
continue;
}
}
Content matchingContent = getSourceContentForTarget(content);
if (matchingContent == null) {
_addedInTarget.add(content);
if (_targetSource.get(e) != null) {
registerModifier(_targetSource.get(e), true, false, false, false, false, false);
}
if (content instanceof Element) {
processToMappingTargetSrc((Element) content);
}
} else {
_targetSource.put(content, matchingContent);
if (!content.getParentElement().equals(getTargetContentForSource(matchingContent.getParentElement()))) {
// element has moved
ParentReferences parentRefs = new ParentReferences(matchingContent.getParentElement(), content.getParentElement(),
content);
if (_movedElements.get(matchingContent) == null) {
System.err.println("moved elements doesn't match in 2nd pass");
} else if (!_movedElements.get(matchingContent).equals(parentRefs)) {
System.err.println("parents of moved elements doesn't match");
}
// _movedElements.put(content, parentRefs);
}
if (content instanceof Element) {
processToMappingTargetSrc((Element) content);
}
}
}
}
public Content getTargetContentForSource(Content srcContent) {
if (_src.getRootElement().equals(srcContent)) {
return _target.getRootElement();
}
if (srcContent instanceof Element) {
String idVal = ((Element) srcContent).getAttributeValue("id");
if (idVal != null) {
return findElementWithId(_target, idVal);
} else {
// shit we don't have an id !!!!
// let's try an idref
String idrefVal = ((Element) srcContent).getAttributeValue("idref");
if (idrefVal != null) {
Content parentTargetElement = getTargetContentForSource(srcContent.getParentElement());
if (parentTargetElement != null) {
return findElementWithIdRef((Element) parentTargetElement, idrefVal);
}
} else {
// shit nor an ID, nor an id ref
Content parentTargetElement = getTargetContentForSource(srcContent.getParentElement());
if (parentTargetElement != null) {
return ((Element) parentTargetElement).getChild(((Element) srcContent).getName());
}
}
}
return null;
} else {
Element parentElement = (Element) getTargetContentForSource(srcContent.getParentElement());
if (parentElement != null) {
return getFirstNonEmptyChildText(parentElement);
}
}
return null;
}
private Content findElementWithIdRef(Element parentTargetElement, String idrefVal) {
Iterator<Element> it = parentTargetElement.getChildren().iterator();
Element item = null;
while (it.hasNext()) {
item = it.next();
if (idrefVal.equals(item.getAttributeValue("idref"))) {
return item;
}
}
return null;
}
public static Content getContentMatchingContent(Content srcContent, Document sourceDocument, Document targetDocument) {
if (sourceDocument.getRootElement().equals(srcContent)) {
return targetDocument.getRootElement();
}
if (srcContent instanceof Element) {
String idVal = ((Element) srcContent).getAttributeValue("id");
if (idVal != null) {
return findElementWithId(targetDocument, idVal);
} else {
Content parentTargetElement = getContentMatchingContent(srcContent.getParentElement(), sourceDocument, targetDocument);
if (parentTargetElement != null) {
return ((Element) parentTargetElement).getChild(((Element) srcContent).getName());
}
}
return null;
} else {
Element parentElement = (Element) getContentMatchingContent(srcContent.getParentElement(), sourceDocument, targetDocument);
if (parentElement != null) {
return getFirstNonEmptyChildText(parentElement);
}
}
return null;
}
private static Content getFirstNonEmptyChildText(Element parentElement) {
for (int i = 0; i < parentElement.getContentSize(); i++) {
Content content = parentElement.getContent(i);
if (content instanceof Text) {
if (((Text) content).getText().trim().length() > 0) {
return content;
}
}
}
return null;
}
public Content getSourceContentForTarget(Content targetContent) {
if (_target.getRootElement().equals(targetContent)) {
return _src.getRootElement();
}
if (targetContent instanceof Element) {
String idVal = ((Element) targetContent).getAttributeValue("id");
if (idVal != null) {
return findElementWithId(_src, idVal);
} else {
Content parentTargetElement = getSourceContentForTarget(targetContent.getParentElement());
if (parentTargetElement != null) {
return ((Element) parentTargetElement).getChild(((Element) targetContent).getName());
}
}
return null;
} else {
Element parentElement = (Element) getSourceContentForTarget(targetContent.getParentElement());
if (parentElement != null) {
return getFirstNonEmptyChildText(parentElement);
}
}
return null;
}
private static Element findElementWithId(Document document, String idRef) {
if (idRef == null) {
return null;
}
Iterator<Element> it = document.getDescendants(new IDFilter(idRef));
if (it.hasNext()) {
return it.next();
}
return null;
}
private static class IDFilter extends ElementFilter {
private String _searchedID;
public IDFilter(String searchedID) {
super();
_searchedID = searchedID;
}
@Override
public Element filter(Object arg0) {
Element element = super.filter(arg0);
if (element != null && _searchedID.equals(element.getAttributeValue("id"))) {
return element;
}
return null;
}
}
public class MatchingTexts extends MatchingXML {
private Text _srcText;
private Text _targetText;
public MatchingTexts(Text src, Text target) {
super();
_srcText = src;
_targetText = target;
}
public Text getSourceText() {
return _srcText;
}
public Text getTargetText() {
return _targetText;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MatchingTexts) {
return getSourceText().equals(((MatchingTexts) obj).getSourceText())
&& getTargetText().equals(((MatchingTexts) obj).getTargetText());
}
return false;
}
@Override
public int hashCode() {
return getSourceText().hashCode() + getTargetText().hashCode();
}
@Override
public boolean isUnchanged() {
if (_srcText.getText() == null) {
return _targetText == null;
}
return _srcText.getText().equals(_targetText.getText());
}
}
public class MatchingElements extends MatchingXML {
private Element _srcElement;
private Element _targetElement;
private AttributesDiff _attributesDiff;
public MatchingElements(Element src, Element target) {
super();
_srcElement = src;
_targetElement = target;
_attributesDiff = new AttributesDiff(_srcElement, _targetElement, DocumentsMapping.this);
}
public AttributesDiff getAttributesDiff() {
return _attributesDiff;
}
public Element getSourceElement() {
return _srcElement;
}
public Element getTargetElement() {
return _targetElement;
}
@Override
public boolean isUnchanged() {
return getAttributesDiff().isUnchanged();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MatchingElements) {
return getSourceElement().equals(((MatchingElements) obj).getSourceElement())
&& getTargetElement().equals(((MatchingElements) obj).getTargetElement());
}
return false;
}
@Override
public int hashCode() {
return getSourceElement().hashCode() + getTargetElement().hashCode();
}
}
public abstract class MatchingXML {
public abstract boolean isUnchanged();
}
public class ParentReferences {
private Element _parentInSource;
private Element _parentInTarget;
private Content _contentInTarget;
public ParentReferences(Element parentInSource, Element parentInTarget, Content contentInTarget) {
super();
_parentInSource = parentInSource;
_parentInTarget = parentInTarget;
_contentInTarget = contentInTarget;
}
public Content getContentInTarget() {
return _contentInTarget;
}
public Element getParentInSource() {
return _parentInSource;
}
public Element getParentInTarget() {
return _parentInTarget;
}
@Override
public String toString() {
return "\t\tParent in source : " + print(getParentInSource()) + "\n\t\tParent in target : " + print(getParentInTarget());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ParentReferences) {
return getParentInSource().equals(((ParentReferences) obj).getParentInSource())
&& getParentInTarget().equals(((ParentReferences) obj).getParentInTarget());
}
return false;
}
@Override
public int hashCode() {
return getParentInSource().hashCode() + getParentInTarget().hashCode();
}
}
public boolean containsUpdateOrModificationsUnder(Content srcContent) {
if (_modifiersFlags.get(srcContent) != null && _modifiersFlags.get(srcContent).isModified()) {
return true;
}
if (srcContent instanceof Text && _modifiersFlags.get(srcContent) == null) {
return false;
}
Iterator<Content> it = ((Element) srcContent).getContent().iterator();
while (it.hasNext()) {
if (containsUpdateOrModificationsUnder(it.next())) {
return true;
}
}
return false;
}
private void registerModifier(Content e, boolean hasNewChild, boolean hasRemovedChild, boolean hasAttributeModified,
boolean hasReceivedChild, boolean hasSendChild, boolean hasTextChanged) {
ModifierFlags flags = _modifiersFlags.get(e);
if (flags == null) {
flags = new ModifierFlags(hasNewChild, hasRemovedChild, hasAttributeModified, hasAttributeModified, hasSendChild,
hasTextChanged);
_modifiersFlags.put(e, flags);
} else {
if (hasNewChild) {
flags.setHasNewChild(true);
}
if (hasRemovedChild) {
flags.setHasRemovedChild(true);
}
if (hasAttributeModified) {
flags.setHasAttributeModified(true);
}
}
}
private class ModifierFlags {
private boolean _hasNewChild;
private boolean _hasRemovedChild;
private boolean _hasAttributeModified;
private boolean _hasTextChanged;
private boolean _hasReceveidChild;
private boolean _hasSendChild;
public ModifierFlags(boolean hasNewChild, boolean hasRemovedChild, boolean hasAttributeModified, boolean hasReceivedChild,
boolean hasSendChild, boolean hasTextChanged) {
super();
_hasAttributeModified = hasAttributeModified;
_hasNewChild = hasNewChild;
_hasRemovedChild = hasRemovedChild;
_hasReceveidChild = hasReceivedChild;
_hasSendChild = hasSendChild;
_hasTextChanged = hasTextChanged;
}
public void setHasTextChanged(boolean b) {
_hasTextChanged = b;
}
public void setHasSendChild(boolean b) {
_hasSendChild = b;
}
public void setHasReceivedChild(boolean b) {
_hasReceveidChild = b;
}
public void setHasAttributeModified(boolean b) {
_hasAttributeModified = b;
}
public void setHasRemovedChild(boolean b) {
_hasRemovedChild = b;
}
public void setHasNewChild(boolean b) {
_hasNewChild = b;
}
public boolean isModified() {
return _hasAttributeModified || _hasNewChild || _hasRemovedChild || _hasSendChild || _hasReceveidChild || _hasTextChanged;
}
}
}