This is an automated email from the ASF dual-hosted git repository. jsorel pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 04d422a Feature : add FeatureComparator utility test class 04d422a is described below commit 04d422a52ebefff4be2d25e442f63dfeaba02412 Author: jsorel AuthorDate: Wed Apr 15 14:25:36 2020 +0200 Feature : add FeatureComparator utility test class --- .../org/apache/sis/feature/FeatureComparator.java | 310 +++++++++++++++++++++ 1 file changed, 310 insertions(+) diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureComparator.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureComparator.java new file mode 100644 index 0000000..5176a7f --- /dev/null +++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureComparator.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.feature; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.Deprecable; +import org.junit.Assert; +import org.opengis.feature.AttributeType; +import org.opengis.feature.Feature; +import org.opengis.feature.FeatureAssociationRole; +import org.opengis.feature.FeatureType; +import org.opengis.feature.IdentifiedType; +import org.opengis.feature.Operation; +import org.opengis.feature.PropertyType; +import org.opengis.util.GenericName; + +/** + * Tool to compare feature and feature types. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +public class FeatureComparator { + + private final Object obj1; + private final Object obj2; + + /** + * The fully-qualified name of properties to ignore in comparisons. This + * collection is initially empty. Users can add or remove elements in this + * collection as they wish. + * + *

+ * The elements shall be names in the form {@code "namespace:name"}, or only + * {@code "name"} if there is no namespace.

+ */ + public final Set ignoredProperties = new HashSet<>(); + /** + * The fully-qualified name of characteristics to ignore in comparisons. + * This collection is initially empty. Users can add or remove elements in + * this collection as they wish. + * + *

+ * The elements shall be names in the form {@code "namespace:name"}, or only + * {@code "name"} if there is no namespace.

+ */ + public final Set ignoredCharacteristics = new HashSet<>(); + + public boolean ignoreDefinition = false; + public boolean ignoreDesignation = false; + public boolean ignoreDescription = false; + + public FeatureComparator(Feature expected, Feature result) { + ArgumentChecks.ensureNonNull("expected", expected); + ArgumentChecks.ensureNonNull("result", result); + this.obj1 = expected; + this.obj2 = result; + } + + public FeatureComparator(FeatureType expected, FeatureType result) { + ArgumentChecks.ensureNonNull("expected", expected); + ArgumentChecks.ensureNonNull("result", result); + this.obj1 = expected; + this.obj2 = result; + } + + /** + * Compare the features or feature types specified at construction time. + */ + public void compare() { + final Path path = new Path(); + if (obj1 instanceof FeatureType) { + compareFeatureType(path, (FeatureType) obj1, (FeatureType) obj2); + } else { + compareFeature(path, (Feature) obj1, (Feature) obj2); + } + } + + private void compareType(Path path, IdentifiedType expected, IdentifiedType result) { + if (expected instanceof FeatureType) { + compareFeatureType(path, (FeatureType) expected, (FeatureType) result); + } else if (expected instanceof PropertyType) { + comparePropertyType(path, (PropertyType) expected, (PropertyType) result); + } else { + Assert.fail(msg(path, "Unexpected type " + expected)); + } + } + + private void compareFeatureType(Path path, FeatureType expected, FeatureType result) { + compareIdentifiedType(path, expected, result); + + Assert.assertEquals(msg(path, "Abstract state differ"), expected.isAbstract(), result.isAbstract()); + Assert.assertEquals(msg(path, "Super types differ"), expected.getSuperTypes(), result.getSuperTypes()); + + List expectedProperties = new ArrayList<>(expected.getProperties(false)); + List resultProperties = new ArrayList<>(result.getProperties(false)); + + while (!expectedProperties.isEmpty()) { + final PropertyType pte = expectedProperties.remove(0); + if (ignoredProperties.contains(pte.getName().toString())) { + continue; + } + Path sub = path.sub(pte.getName().toString()); + PropertyType ptr = find(sub, resultProperties, pte.getName()); + resultProperties.remove(ptr); + comparePropertyType(sub, pte, ptr); + } + while (!resultProperties.isEmpty()) { + final PropertyType pte = resultProperties.remove(0); + if (ignoredProperties.contains(pte.getName().toString())) { + continue; + } + Path sub = path.sub(pte.getName().toString()); + Assert.fail(msg(sub, "Result type contains a property not declared in expected type : " + pte.getName())); + } + + } + + private void compareFeature(Path path, Feature expected, Feature result) { + compareFeatureType(path, expected.getType(), result.getType()); + + Collection properties = expected.getType().getProperties(true); + for (PropertyType pte : properties) { + if (ignoredProperties.contains(pte.getName().toString())) { + continue; + } + Path sub = path.sub(pte.getName().toString()); + + Object expectedValue = expected.getPropertyValue(pte.getName().toString()); + Object resultValue = result.getPropertyValue(pte.getName().toString()); + if (!(expectedValue instanceof Collection)) { + expectedValue = (expectedValue == null) ? Collections.EMPTY_LIST : Arrays.asList(expectedValue); + } + if (!(resultValue instanceof Collection)) { + resultValue = (resultValue == null) ? Collections.EMPTY_LIST : Arrays.asList(resultValue); + } + + Collection expectedCol = (Collection) expectedValue; + Collection resultCol = (Collection) resultValue; + + if (expectedCol.size() != resultCol.size()) { + Assert.fail(msg(sub, "Number of values differ, expected " + expectedCol.size() + " but was " + resultCol.size())); + } + + Iterator expectedIte = expectedCol.iterator(); + Iterator resultIte = resultCol.iterator(); + + while (expectedIte.hasNext()) { + Object subExpVal = expectedIte.next(); + Object subResVal = resultIte.next(); + + if (subExpVal instanceof Feature) { + compareFeature(path, (Feature) subExpVal, (Feature) subResVal); + } else { + Assert.assertEquals(subExpVal, subResVal); + } + } + } + } + + private void comparePropertyType(Path path, PropertyType expected, PropertyType result) { + + if (expected instanceof AttributeType) { + if (!(result instanceof AttributeType)) { + Assert.fail(msg(path, "Expected an AttributeType for name " + ((AttributeType) expected).getName() + " but found a " + result)); + } + compareAttribute(path, (AttributeType) expected, (AttributeType) result); + + } else if (expected instanceof FeatureAssociationRole) { + if (!(result instanceof FeatureAssociationRole)) { + Assert.fail(msg(path, "Expected a FeatureAssociationRole for name " + ((AttributeType) expected).getName() + " but found a " + result)); + } + compareFeatureAssociationRole(path, (FeatureAssociationRole) expected, (FeatureAssociationRole) result); + + } else if (expected instanceof Operation) { + if (!(result instanceof Operation)) { + Assert.fail(msg(path, "Expected an Operation for name " + ((AttributeType) expected).getName() + " but found a " + result)); + } + compareOperation(path, (Operation) expected, (Operation) result); + } + } + + private void compareAttribute(Path path, AttributeType expected, AttributeType result) { + compareIdentifiedType(path, expected, result); + Assert.assertEquals(msg(path, "Value classe differ"), expected.getValueClass(), expected.getValueClass()); + Assert.assertEquals(msg(path, "Default value differ"), expected.getDefaultValue(), expected.getDefaultValue()); + + Map> expCharacteristics = expected.characteristics(); + Map> resCharacteristics = result.characteristics(); + + final List expKeys = new ArrayList<>(expCharacteristics.keySet()); + final List resKeys = new ArrayList<>(resCharacteristics.keySet()); + + while (!expKeys.isEmpty()) { + final String pte = expKeys.remove(0); + if (ignoredCharacteristics.contains(pte)) { + continue; + } + AttributeType exp = expCharacteristics.get(pte); + AttributeType res = resCharacteristics.get(pte); + resKeys.remove(pte); + comparePropertyType(path.sub("characteristic(" + pte + ")"), exp, res); + } + while (!resKeys.isEmpty()) { + final String pte = resKeys.remove(0); + if (ignoredCharacteristics.contains(pte)) { + continue; + } + Assert.fail(msg(path, "Result type contains a characteristic not declared in expected type : " + pte)); + } + + } + + private void compareFeatureAssociationRole(Path path, FeatureAssociationRole expected, FeatureAssociationRole result) { + compareIdentifiedType(path, expected, result); + + Assert.assertEquals(msg(path, "Minimum occurences differ"), expected.getMinimumOccurs(), result.getMinimumOccurs()); + Assert.assertEquals(msg(path, "Maximum occurences differ"), expected.getMaximumOccurs(), result.getMaximumOccurs()); + compareFeatureType(path.sub("association-valuetype"), expected.getValueType(), result.getValueType()); + } + + private void compareOperation(Path path, Operation expected, Operation result) { + compareIdentifiedType(path, expected, result); + Assert.assertEquals(expected.getParameters(), result.getParameters()); + compareType(path.sub("operation-result(" + expected.getResult().getName().toString() + ")"), expected.getResult(), result.getResult()); + } + + private void compareIdentifiedType(Path path, IdentifiedType expected, IdentifiedType result) { + Assert.assertEquals(msg(path, "Name differ"), expected.getName(), result.getName()); + if (!ignoreDefinition) { + Assert.assertEquals(msg(path, "Definition differ"), expected.getDefinition(), result.getDefinition()); + } + if (!ignoreDesignation) { + Assert.assertEquals(msg(path, "Designation differ"), expected.getDesignation(), result.getDesignation()); + } + if (!ignoreDescription) { + Assert.assertEquals(msg(path, "Description differ"), expected.getDescription(), result.getDescription()); + } + + //check deprecable + if (expected instanceof Deprecable) { + if (result instanceof Deprecable) { + boolean dep1 = ((Deprecable) expected).isDeprecated(); + boolean dep2 = ((Deprecable) result).isDeprecated(); + if (dep1 != dep2) { + Assert.fail(msg(path, "Deprecated state differ, " + dep1 + " in expected " + dep2 + " in result")); + } + } + } + } + + private static PropertyType find(Path path, Collection properties, GenericName name) { + for (PropertyType pt : properties) { + if (pt.getName().equals(name)) { + return pt; + } + } + Assert.fail(msg(path, "Property not found for name " + name)); + return null; + } + + private static String msg(Path path, String errorMessage) { + return path.toString() + " " + errorMessage; + } + + private static class Path { + + private final List segments = new ArrayList<>(); + + public Path sub(String segment) { + Path p = new Path(); + p.segments.addAll(segments); + p.segments.add(segment); + return p; + } + + @Override + public String toString() { + final StringJoiner sj = new StringJoiner(" > "); + segments.stream().forEach(sj::add); + return "[" + sj.toString() + "]"; + } + } +}