/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.instance.model;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.namespace.QName;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty;
/**
* Instance utility functions.
*
* @author Kai Schwierczek
*/
public final class InstanceUtil {
private InstanceUtil() {
// static utility class
}
/**
* Checks whether the two given instances equal each other disregarding
* their data set.
*
* @param a the first instance
* @param b the second instance
* @param propertyOrderRelevant whether the order of properties of the same
* name is relevant or not
* @return true, iff both instances are equal to each other except for their
* data set
*/
public static boolean instanceEqual(Instance a, Instance b, boolean propertyOrderRelevant) {
if (a == b)
return true;
if (a == null || b == null)
return false;
// compare value
// XXX other checks than equals possible?
if (!equals(a.getValue(), b.getValue()))
return false;
// check groups properties
return groupEqual(a, b, propertyOrderRelevant);
}
/**
* Equals implementation comparing two objects, with some improvements and
* adaptations.
*
* @param o1 the first object
* @param o2 the second object
* @return if the two objects are deemed equal
*/
private static boolean equals(Object o1, Object o2) {
if (o1 != null && o2 != null) {
// special case: arrays
if (o1.getClass().isArray() && o2.getClass().isArray()) {
return arrayToList(o1).equals(arrayToList(o2));
}
// special case: geometry properties
if (o1 instanceof GeometryProperty<?> && o2 instanceof GeometryProperty<?>) {
GeometryProperty<?> g1 = (GeometryProperty<?>) o1;
GeometryProperty<?> g2 = (GeometryProperty<?>) o2;
if (g1.getGeometry() == null && g2.getGeometry() == null) {
return true;
}
else if (g1.getGeometry() != null && g2.getGeometry() != null) {
boolean crsEquals;
if (g1.getCRSDefinition() != null && g2.getCRSDefinition() != null) {
crsEquals = Objects.equal(g1.getCRSDefinition().getCRS(), g2
.getCRSDefinition().getCRS());
}
else {
crsEquals = Objects.equal(g1.getCRSDefinition(), g2.getCRSDefinition());
}
// XXX do conversion of geometry?
// topological comparison (added 0.005 as tolerance for
// testing purpose)
boolean geometryEquals = g1.getGeometry().equalsExact(g2.getGeometry(), 0.005);
// geometryEquals && crsEquals;
return geometryEquals || crsEquals;
}
else {
return false;
}
}
}
return Objects.equal(o1, o2);
}
private static List<?> arrayToList(Object array) {
if (array instanceof byte[]) {
return Bytes.asList((byte[]) array);
}
if (array instanceof int[]) {
return Ints.asList((int[]) array);
}
if (array instanceof short[]) {
return Shorts.asList((short[]) array);
}
if (array instanceof long[]) {
return Longs.asList((long[]) array);
}
if (array instanceof float[]) {
return Floats.asList((float[]) array);
}
if (array instanceof double[]) {
return Doubles.asList((double[]) array);
}
if (array instanceof char[]) {
return Chars.asList((char[]) array);
}
if (array instanceof boolean[]) {
return Booleans.asList((boolean[]) array);
}
return Arrays.asList((Object[]) array);
}
/**
* Check if an instance is present in the given candidates. If found, will
* remove the match from the candidates collection.
*
* @param instance the instance to test
* @param candidates the candidates to compare the instance against
* @return the error message if the check failed, otherwise
* <code>null</code>
*/
public static String checkInstance(Instance instance, Collection<Instance> candidates) {
boolean found = false;
Iterator<Instance> candidatesIter = candidates.iterator();
while (!found && candidatesIter.hasNext()) {
if (InstanceUtil.instanceEqual(instance, candidatesIter.next(), false)) {
candidatesIter.remove();
found = true;
}
}
if (!found) {
StringBuilder sb = new StringBuilder();
sb.append("Could not find matching instance for: \n");
sb.append(InstanceUtil.instanceToString(instance));
sb.append("\n inside the available ones: \n");
for (Instance candidate : candidates) {
sb.append(InstanceUtil.instanceToString(candidate));
}
String message = sb.toString();
return message;
}
return null;
}
/**
* Checks whether the two given groups equal each other.
*
* @param a the first group
* @param b the second group
* @param propertyOrderRelevant whether the order of properties of the same
* name is relevant or not
* @return true, if both groups are equal to each
*/
public static boolean groupEqual(Group a, Group b, boolean propertyOrderRelevant) {
if (a == b)
return true;
// compare definitions
if (!Objects.equal(a.getDefinition(), b.getDefinition()))
return false;
// check property count
Iterable<QName> aProperties = a.getPropertyNames();
if (Iterables.size(aProperties) != Iterables.size(b.getPropertyNames()))
return false;
// iterate over a properties
for (QName aPropertyName : aProperties) {
Object[] aProperty = a.getProperty(aPropertyName);
Object[] bProperty = b.getProperty(aPropertyName);
// check whether the property exists (in the same amount)
if (bProperty == null || bProperty.length != aProperty.length)
return false;
if (propertyOrderRelevant) {
// simply iterate over the property array once
for (int i = 0; i < aProperty.length; i++)
if (!propertyValueEquals(aProperty[i], bProperty[i], propertyOrderRelevant))
return false;
}
else {
// check whether each property value of a has an equal property
// value in b
List<Object> bPropertyList = new LinkedList<Object>(Arrays.asList(bProperty));
for (Object aPropertyValue : aProperty) {
Iterator<Object> bPropertyValueIterator = bPropertyList.iterator();
boolean match = false;
while (!match && bPropertyValueIterator.hasNext()) {
Object bPropertyValue = bPropertyValueIterator.next();
match = propertyValueEquals(aPropertyValue, bPropertyValue,
propertyOrderRelevant);
if (match)
bPropertyValueIterator.remove();
}
if (!match)
return false;
}
}
}
return true;
}
private static boolean propertyValueEquals(Object a, Object b, boolean propertyOrderRelevant) {
if (a == b)
return true;
// check if a is an instance or a group for specialized check
// XXX other method than equals if it is not a group or instance?
if (a instanceof Instance) {
if (b instanceof Instance
&& instanceEqual((Instance) a, (Instance) b, propertyOrderRelevant))
return true;
}
else if (a instanceof Group) {
if (b instanceof Group && groupEqual((Group) a, (Group) b, propertyOrderRelevant))
return true;
}
// Two BigDecimal objects that are equal in value but have a different
// scale (like 2.0 and 2.00) should consider as equal.
else if (a instanceof BigDecimal && b instanceof BigDecimal) {
BigDecimal x = (BigDecimal) a;
BigDecimal y = (BigDecimal) b;
return x.compareTo(y) == 0;
}
else if (equals(a, b))
return true;
return false;
}
// TODO better output for values in case of Collections?
/**
* Returns a string representation of the given instance.
*
* @param instance the instance
* @return a string representation of the given instance
*/
public static String instanceToString(Instance instance) {
StringBuilder builder = new StringBuilder();
if (instance.getValue() != null)
builder.append("value=").append(instance.getValue()).append('\n');
builder.append("properties=[\n");
builder.append(indent(groupToString(instance))).append("\n]");
return builder.toString();
}
/**
* Returns a string representation of the given group.
*
* @param group the group
* @return a string representation of the given group
*/
public static String groupToString(Group group) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (QName property : group.getPropertyNames()) {
if (first)
first = false;
else
builder.append('\n');
builder.append(property.toString()).append("=[\n");
for (Object propertyValue : group.getProperty(property)) {
String representation;
if (propertyValue instanceof Instance)
representation = instanceToString((Instance) propertyValue);
else if (propertyValue instanceof Group)
representation = groupToString((Group) propertyValue);
else if (propertyValue != null) {
if (propertyValue.getClass().isArray()) {
representation = arrayToList(propertyValue).toString();
}
else {
representation = propertyValue.toString();
}
}
else
representation = "<null>";
builder.append(indent(representation)).append('\n');
}
builder.append("]");
}
return builder.toString();
}
/**
* Indents the given string with one tab.
*
* @param string the string to indent
* @return the string indented with one tab
*/
private static String indent(String string) {
return "\t" + string.replace("\n", "\n\t");
}
/**
* Creates a list of instances out of a FamilyInstance
*
* @param fi the FamilyInstance, may be <code>null</code>
* @return a collection of instances or an empty list
*/
public static Collection<Instance> getInstanceOutOfFamily(FamilyInstance fi) {
Collection<Instance> result = new ArrayList<Instance>();
if (fi != null) {
result.add(fi);
if (!fi.getChildren().isEmpty()) {
for (FamilyInstance inst : fi.getChildren()) {
if (!inst.getChildren().isEmpty()) {
result.addAll(getInstanceOutOfFamily(inst));
}
else
result.add(inst);
}
}
}
return result;
}
}