XppDomComparator.java
/*
* Copyright (C) 2011, 2014, 2015 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 11. August 2011 by Joerg Schaible.
*/
package com.thoughtworks.xstream.io.xml.xppdom;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
/**
* Comparator for {@link XppDom}. Comparator can trace the XPath where the comparison failed.
*
* @author Jörg Schaible
* @since 1.4.1
*/
public class XppDomComparator implements Comparator<XppDom> {
private final ThreadLocal<String> xpath;
/**
* Creates a new Xpp3DomComparator object.
*
* @since 1.4.1
*/
public XppDomComparator() {
this(null);
}
/**
* Creates a new Xpp3DomComparator object with XPath identification.
*
* @param xpath the reference for the XPath
* @since 1.4.1
*/
public XppDomComparator(final ThreadLocal<String> xpath) {
this.xpath = xpath;
}
@Override
public int compare(final XppDom dom1, final XppDom dom2) {
final StringBuilder xpath = new StringBuilder("/");
final int s = compareInternal(dom1, dom2, xpath, -1);
if (this.xpath != null) {
if (s != 0) {
this.xpath.set(xpath.toString());
} else {
this.xpath.set(null);
}
}
return s;
}
private int compareInternal(final XppDom dom1, final XppDom dom2, final StringBuilder xpath, final int count) {
final int pathlen = xpath.length();
final String name = dom1.getName();
int s = name.compareTo(dom2.getName());
xpath.append(name);
if (count >= 0) {
xpath.append('[').append(count).append(']');
}
if (s != 0) {
xpath.append('?');
return s;
}
final String[] attributes = dom1.getAttributeNames();
final String[] attributes2 = dom2.getAttributeNames();
final int len = attributes.length;
s = attributes2.length - len;
if (s != 0) {
xpath.append("::count(@*)");
return s < 0 ? 1 : -1;
}
Arrays.sort(attributes);
Arrays.sort(attributes2);
for (int i = 0; i < len; ++i) {
final String attribute = attributes[i];
s = attribute.compareTo(attributes2[i]);
if (s != 0) {
xpath.append("[@").append(attribute).append("?]");
return s;
}
s = dom1.getAttribute(attribute).compareTo(dom2.getAttribute(attribute));
if (s != 0) {
xpath.append("[@").append(attribute).append(']');
return s;
}
}
final int children = dom1.getChildCount();
s = dom2.getChildCount() - children;
if (s != 0) {
xpath.append("::count(*)");
return s < 0 ? 1 : -1;
}
if (children > 0) {
if (dom1.getValue() != null || dom2.getValue() != null) {
throw new IllegalArgumentException("XppDom cannot handle mixed mode at " + xpath + "::text()");
}
xpath.append('/');
final Map<String, int[]> names = new HashMap<>();
for (int i = 0; i < children; ++i) {
final XppDom child1 = dom1.getChild(i);
final XppDom child2 = dom2.getChild(i);
final String child = child1.getName();
if (!names.containsKey(child)) {
names.put(child, new int[1]);
}
s = compareInternal(child1, child2, xpath, names.get(child)[0]++);
if (s != 0) {
return s;
}
}
} else {
final String value2 = dom2.getValue();
final String value1 = dom1.getValue();
if (value1 == null) {
s = value2 == null ? 0 : -1;
} else {
s = value2 == null ? 1 : value1.compareTo(value2);
}
if (s != 0) {
xpath.append("::text()");
return s;
}
}
xpath.setLength(pathlen);
return s;
}
}