From 5bd98b0a7596a32d09b076a9acc3f339a8ab6dda Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Fri, 6 Dec 2024 00:17:38 -0500 Subject: [PATCH] Added support for the Metapath function deep-equal. --- .../core/metapath/cst/StaticFunctionCall.java | 3 +- .../core/metapath/cst/path/Axis.java | 79 ++++++++- .../core/metapath/function/IFunction.java | 7 + .../function/impl/AbstractFunction.java | 58 ++++-- .../function/impl/OperationFunctions.java | 16 ++ .../library/DefaultFunctionLibrary.java | 3 +- .../function/library/FnDeepEqual.java | 70 ++++++++ .../metapath/function/library/FnIndexOf.java | 5 +- .../core/metapath/impl/AbstractArrayItem.java | 27 +++ .../core/metapath/impl/AbstractMapItem.java | 30 ++++ .../core/metapath/impl/AbstractSequence.java | 28 +++ .../metapath/impl/AbstractStringMapKey.java | 2 +- .../core/metapath/item/ICollectionValue.java | 14 ++ .../metapath/item/atomic/IAnyAtomicItem.java | 17 ++ .../item/atomic/impl/AbstractUriItem.java | 1 + .../item/atomic/impl/AnyUriItemImpl.java | 1 - .../metapath/item/node/AbstractNodeItem.java | 21 ++- .../metapath/item/node/IAssemblyNodeItem.java | 17 ++ .../metapath/item/node/IDocumentNodeItem.java | 21 +++ .../item/node/IFeatureAtomicValuedItem.java | 2 + .../node/IFeatureNoDataAtomicValuedItem.java | 4 +- .../metapath/item/node/IFieldNodeItem.java | 24 ++- .../metapath/item/node/IFlagNodeItem.java | 28 ++- .../metapath/item/node/IModuleNodeItem.java | 19 +- .../core/metapath/item/node/INodeItem.java | 26 ++- .../metapath/item/node/NodeComparators.java | 166 ++++++++++++++++++ .../metaschema/core/qname/IEnhancedQName.java | 21 ++- .../metaschema/core/qname/QNameCache.java | 11 ++ .../core/util/CustomCollectors.java | 2 +- .../function/library/FnDeepEqualTest.java | 48 +++++ 30 files changed, 733 insertions(+), 38 deletions(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqual.java create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/NodeComparators.java create mode 100644 core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqualTest.java diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StaticFunctionCall.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StaticFunctionCall.java index 2cd85e4ec..cc1ee416e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StaticFunctionCall.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/StaticFunctionCall.java @@ -99,7 +99,8 @@ public RESULT accept(IExpressionVisitor visit @Override public ISequence accept(DynamicContext dynamicContext, ISequence focus) { List> arguments = ObjectUtils.notNull(this.arguments.stream() - .map(expression -> expression.accept(dynamicContext, focus)).collect(Collectors.toList())); + .map(expression -> expression.accept(dynamicContext, focus).contentsAsSequence()) + .collect(Collectors.toList())); IFunction function = getFunction(); return function.execute(arguments, dynamicContext, focus); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java index 024e270d8..05bec084e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/path/Axis.java @@ -21,20 +21,97 @@ import edu.umd.cs.findbugs.annotations.NonNull; -@SuppressWarnings("PMD.ShortClassName") // intentional +/** + * An implementation of Metapath + * axes. + */ +@SuppressWarnings("PMD.ShortClassName") public enum Axis implements IExpression { + /** + * The {@code self::} axis, referring to the current context node. + */ SELF(Stream::of), + /** + * The {@code parent::} axis, referring to the current context node's parent. + * + * @see INodeItem#getParentNodeItem() + */ PARENT(focus -> Stream.ofNullable(focus.getParentNodeItem())), + /** + * The {@code flag::} axis, referring to the current context node's flags. + * + * @see INodeItem#getFlags() + */ FLAG(INodeItem::flags), + /** + * The {@code ancestor::} axis, referring to the current context node's + * parentage. + * + * @see INodeItem#ancestor() + */ ANCESTOR(INodeItem::ancestor), + /** + * The {@code ancestor-or-self::} axis, referring to the current context node + * and its parentage. + * + * @see INodeItem#ancestorOrSelf() + */ ANCESTOR_OR_SELF(INodeItem::ancestorOrSelf), + /** + * The {@code children::} axis, referring to the current context node's direct + * children. + * + * @see INodeItem#modelItems() + */ CHILDREN(INodeItem::modelItems), + /** + * The {@code descendant::} axis, referring to all of the current context node's + * descendants (i.e., the children, the children of the children, etc). + * + * @see INodeItem#descendant() + */ DESCENDANT(INodeItem::descendant), + /** + * The {@code descendant-or-self::} axis, referring to the current context node + * and all of the current context node's descendants (i.e., the children, the + * children of the children, etc). + * + * @see INodeItem#descendantOrSelf() + */ DESCENDANT_OR_SELF(INodeItem::descendantOrSelf), + /** + * The {@code following-sibling::} axis, referring to those children of the + * context node's parent that occur after the context node in + * document + * order. + */ FOLLOWING_SIBLING(INodeItem::followingSibling), + /** + * The {@code preceding-sibling::} axis, referring to those children of the + * context node's parent that occur before the context node in + * document + * order. + */ PRECEDING_SIBLING(INodeItem::precedingSibling), + /** + * The {@code preceding-sibling::} axis, referring to all nodes that are + * descendants of the root of the tree in which the context node is found, are + * not descendants of the context node, and occur after the context node in + * document + * order. + */ FOLLOWING(INodeItem::following), + /** + * The {@code preceding-sibling::} axis, referring to all nodes that are + * descendants of the root of the tree in which the context node is found, are + * not ancestors of the context node, and occur before the context node in + * document + * order. + */ PRECEDING(INodeItem::preceding), + /** + * This axis is not supported. + */ NAMESPACE(focus -> { throw new StaticMetapathException( StaticMetapathException.AXIS_NAMESPACE_UNSUPPORTED, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java index 815a5f015..b58663e35 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IFunction.java @@ -9,6 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.MetapathException; import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.ISequence; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; @@ -180,6 +181,12 @@ default boolean isArityUnbounded() { // */ // boolean isSupported(List> arguments); + @Override + default boolean deepEquals(ICollectionValue other) { + // this is the expected result + return false; + } + /** * Execute the function with the provided {@code arguments}, using the provided * {@code DynamicContext} and {@code focus}. diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java index ba0a1fc70..2c25a9d01 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/AbstractFunction.java @@ -8,7 +8,6 @@ import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; import gov.nist.secauto.metaschema.core.metapath.MetapathException; -import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.function.CalledContext; import gov.nist.secauto.metaschema.core.metapath.function.IArgument; import gov.nist.secauto.metaschema.core.metapath.function.IFunction; @@ -19,6 +18,7 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; +import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType; import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.CollectionUtil; @@ -32,12 +32,38 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +/** + * This abstract implementation provides common functionality shared by all + * functions. + */ public abstract class AbstractFunction implements IFunction { @NonNull private final IEnhancedQName qname; @NonNull private final List arguments; + /** + * Construct a new function using the provided name and namespace, used together + * to form the function's qualified name, and the provided arguments. + *

+ * This constructor is equivalent to calling: + * + *

+   * {@code
+   * String name = ...;
+   * String namespace = ...;
+   * List arguments = ...;
+   * new AbstractFunction(IEnhancedQName.of(namespace, name), arguments);
+   * }
+   * 
+ * + * @param name + * the function's name + * @param namespace + * the function's namespace + * @param arguments + * the function's arguments + */ protected AbstractFunction( @NonNull String name, @NonNull String namespace, @@ -45,6 +71,14 @@ protected AbstractFunction( this(IEnhancedQName.of(namespace, name), arguments); } + /** + * Construct a new function using the provided qualified name and arguments. + * + * @param qname + * the function's qualified name + * @param arguments + * the function's arguments + */ protected AbstractFunction( @NonNull IEnhancedQName qname, @NonNull List arguments) { @@ -118,26 +152,24 @@ public static List> convertArguments( private static ISequence convertArgument( @NonNull IArgument argument, @NonNull ISequence parameter) { + ISequenceType sequenceType = argument.getSequenceType(); + // apply occurrence - ISequence retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter); + ISequence retval = sequenceType.getOccurrence().getSequenceHandler().handle(parameter); // apply function conversion and type promotion to the parameter if (!retval.isEmpty()) { - IItemType type = argument.getSequenceType().getType(); + IItemType type = sequenceType.getType(); // this is not required to be an empty sequence retval = convertSequence(argument, retval, type); // verify resulting values - Class argumentClass = type.getItemClass(); - for (IItem item : retval.getValue()) { - Class itemClass = item.getClass(); - if (!argumentClass.isAssignableFrom(itemClass)) { - throw new InvalidTypeMetapathException( - item, - String.format("The type '%s' is not a subtype of '%s'", - StaticContext.lookupItemType(itemClass), - type)); - } + if (!sequenceType.matches(retval)) { + throw new InvalidTypeMetapathException( + null, + String.format("The argument '%s' is not a '%s'", + retval.toSignature(), + sequenceType.toSignature())); } } return retval; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java index 8aca81644..c542a209b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/impl/OperationFunctions.java @@ -9,6 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.function.DateTimeFunctionException; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem; @@ -20,6 +21,8 @@ import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDurationItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUntypedAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IYearMonthDurationItem; import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -1066,4 +1069,17 @@ public static IBooleanItem opBooleanLessThan(@Nullable IBooleanItem arg1, @Nulla return IBooleanItem.valueOf(!left && right); } + + public static boolean opSameKey(@NonNull IAnyAtomicItem k1, @NonNull IAnyAtomicItem k2) { + boolean retval; + if ((k1 instanceof IStringItem || k1 instanceof IAnyUriItem || k1 instanceof IUntypedAtomicItem) + && (k2 instanceof IStringItem || k2 instanceof IAnyUriItem || k2 instanceof IUntypedAtomicItem)) { + retval = k1.asString().equals(k2.asString()); + } else if (k1 instanceof IDecimalItem && k2 instanceof IDecimalItem) { + retval = ((IDecimalItem) k1).asDecimal().equals(((IDecimalItem) k2).asDecimal()); + } else { + retval = k1.deepEquals(k2); + } + return retval; + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java index 6501c8426..e6520ca76 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java @@ -62,7 +62,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // https://www.w3.org/TR/xpath-functions-31/#func-day-from-date // https://www.w3.org/TR/xpath-functions-31/#func-day-from-dateTime // https://www.w3.org/TR/xpath-functions-31/#func-days-from-duration - // P1: https://www.w3.org/TR/xpath-functions-31/#func-deep-equal + // https://www.w3.org/TR/xpath-functions-31/#func-deep-equal + registerFunction(FnDeepEqual.SIGNATURE_TWO_ARG); // P1: https://www.w3.org/TR/xpath-functions-31/#func-distinct-values // https://www.w3.org/TR/xpath-functions-31/#func-doc registerFunction(FnDoc.SIGNATURE); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqual.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqual.java new file mode 100644 index 000000000..e175986c7 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqual.java @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements fn:deep-equal + * functions. + *

+ * This implementation does not implement the three-arg variant with collation + * at this time. + */ +public final class FnDeepEqual { + @NonNull + private static final String NAME = "deep-equal"; + @NonNull + static final IFunction SIGNATURE_TWO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusIndependent() + .argument(IArgument.builder() + .name("parameter1") + .type(IItem.type()) + .zeroOrMore() + .build()) + .argument(IArgument.builder() + .name("parameter2") + .type(IItem.type()) + .zeroOrMore() + .build()) + .returnType(IBooleanItem.type()) + .returnOne() + .functionHandler(FnDeepEqual::executeTwoArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeTwoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + ISequence parameter1 = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0))); + ISequence parameter2 = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1))); + + return ISequence.of(IBooleanItem.valueOf(parameter1.deepEquals(parameter2))); + } + + private FnDeepEqual() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java index e92baabe3..3b12ed4ff 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java @@ -64,10 +64,7 @@ private static ISequence executeTwoArg(@NonNull IFunction function ISequence seq = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0))); IAnyAtomicItem search = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true))); - if (seq.size() == 0) { - return ISequence.empty(); - } - return fnIndexOf(seq, search); + return seq.isEmpty() ? ISequence.empty() : fnIndexOf(seq, search); } /** diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java index 28a4ffbcf..c8315574a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractArrayItem.java @@ -19,6 +19,7 @@ import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.EnumSet; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; @@ -136,6 +137,32 @@ public boolean equals(Object other) { || other instanceof IArrayItem && getValue().equals(((IArrayItem) other).getValue()); } + @SuppressWarnings("PMD.OnlyOneReturn") + @Override + public boolean deepEquals(ICollectionValue other) { + if (!(other instanceof IArrayItem)) { + return false; + } + + IArrayItem otherArray = (IArrayItem) other; + if (size() != otherArray.size()) { + return false; + } + + Iterator thisIterator = iterator(); + Iterator otherIterator = otherArray.iterator(); + boolean retval = true; + while (thisIterator.hasNext() && otherIterator.hasNext()) { + ICollectionValue i1 = thisIterator.next(); + ICollectionValue i2 = otherIterator.next(); + if (!i1.deepEquals(i2)) { + retval = false; + break; + } + } + return retval; + } + @Override public String toSignature() { return ObjectUtils.notNull(stream() diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java index ce6491b33..ee9925fa3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractMapItem.java @@ -21,7 +21,9 @@ import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.EnumSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -147,6 +149,34 @@ public boolean equals(Object other) { || other instanceof IMapItem && getValue().equals(((IMapItem) other).getValue()); } + @SuppressWarnings("PMD.OnlyOneReturn") + @Override + public boolean deepEquals(ICollectionValue other) { + if (!(other instanceof IMapItem)) { + return false; + } + + IMapItem otherItem = (IMapItem) other; + if (size() != otherItem.size()) { + return false; + } + + Iterator> thisIterator = entrySet().iterator(); + Iterator> otherIterator = otherItem.entrySet().iterator(); + boolean retval = true; + while (thisIterator.hasNext() && otherIterator.hasNext()) { + Map.Entry i1 = thisIterator.next(); + Map.Entry i2 = otherIterator.next(); + + retval = i1.getKey().equals(i2.getKey()) + && i1.getValue().deepEquals(i2.getValue()); + if (!retval) { + break; + } + } + return retval; + } + @Override public String toSignature() { return ObjectUtils.notNull(entrySet().stream() diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java index b6e762a8a..0e30d4051 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractSequence.java @@ -5,10 +5,12 @@ package gov.nist.secauto.metaschema.core.metapath.impl; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.IItem; import gov.nist.secauto.metaschema.core.metapath.item.ISequence; import gov.nist.secauto.metaschema.core.util.ObjectUtils; +import java.util.Iterator; import java.util.stream.Collectors; import edu.umd.cs.findbugs.annotations.NonNull; @@ -47,6 +49,32 @@ public boolean equals(Object other) { || other instanceof ISequence && getValue().equals(((ISequence) other).getValue()); } + @SuppressWarnings("PMD.OnlyOneReturn") + @Override + public boolean deepEquals(ICollectionValue other) { + if (!(other instanceof ISequence)) { + return false; + } + + ISequence otherSequence = (ISequence) other; + if (size() != otherSequence.size()) { + return false; + } + + Iterator thisIterator = iterator(); + Iterator otherIterator = otherSequence.iterator(); + boolean retval = true; + while (thisIterator.hasNext() && otherIterator.hasNext()) { + IItem i1 = thisIterator.next(); + IItem i2 = otherIterator.next(); + if (!i1.deepEquals(i2)) { + retval = false; + break; + } + } + return retval; + } + @Override public int hashCode() { return getValue().hashCode(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java index be145cd1a..86b895874 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/AbstractStringMapKey.java @@ -21,7 +21,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { return this == obj || - obj instanceof AbstractStringMapKey + obj instanceof IMapKey && getKey().asStringItem().equals(((AbstractStringMapKey) obj).getKey().asStringItem()); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/ICollectionValue.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/ICollectionValue.java index afa063800..7a73f4b7f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/ICollectionValue.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/ICollectionValue.java @@ -95,6 +95,20 @@ static Stream normalizeAsItems(@NonNull ICollectionValue value) @NonNull Stream flatten(); + /** + * Determine if this and the other value are deeply equal. + *

+ * Item equality is defined by the + * XPath 3.1 + * fn:deep-equal specification. + * + * @param other + * the other value to compare to this value to + * @return the {@code true} if the two values are equal, or {@code false} + * otherwise + */ + boolean deepEquals(ICollectionValue other); + /** * Get a representation of the value based on its type signature. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java index 73be4d6ba..41397870b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IAnyAtomicItem.java @@ -6,12 +6,15 @@ package gov.nist.secauto.metaschema.core.metapath.item.atomic; import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; +import gov.nist.secauto.metaschema.core.metapath.function.ComparisonFunctions; import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.IItemVisitor; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IMapKey; import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.stream.Stream; @@ -126,6 +129,20 @@ static IAnyAtomicItem cast(@NonNull IAnyAtomicItem item) { */ int compareTo(@NonNull IAnyAtomicItem other); + @Override + default boolean deepEquals(ICollectionValue other) { + boolean retval; + try { + retval = other instanceof IAnyAtomicItem + && ComparisonFunctions.valueCompairison(this, ComparisonFunctions.Operator.EQ, (IAnyAtomicItem) other) + .toBoolean(); + } catch (InvalidTypeMetapathException ex) { + // incompatible types are a non-match + retval = false; + } + return retval; + } + @Override default void accept(IItemVisitor visitor) { visitor.visit(this); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractUriItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractUriItem.java index eaea731f1..530f153bc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractUriItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AbstractUriItem.java @@ -58,6 +58,7 @@ protected String getValueSignature() { public boolean equals(Object obj) { return this == obj || obj instanceof IAnyUriItem && compareTo((IAnyUriItem) obj) == 0; + } private final class MapKey diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AnyUriItemImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AnyUriItemImpl.java index 5e24ee30e..7df2600cb 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AnyUriItemImpl.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/impl/AnyUriItemImpl.java @@ -32,5 +32,4 @@ public AnyUriItemImpl(@NonNull URI value) { public UriAdapter getJavaTypeAdapter() { return MetaschemaDataTypeProvider.URI; } - } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractNodeItem.java index 58aff1707..a35294c98 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/AbstractNodeItem.java @@ -5,30 +5,34 @@ import edu.umd.cs.findbugs.annotations.Nullable; +/** + * A common base class for node item implementations. + */ public abstract class AbstractNodeItem implements INodeItem { /** * Generates a string signature for this node item in the format: - * {@code type⪻location_metapath⪼} or {@code type⪻location_metapath⪼(value)} - * where: + * {@code type\u2ABBlocation_metapath\u2ABC} or + * {@code type\u2ABBlocation_metapath\u2ABC(value)} where: *

    *
  • type: The node type signature
  • *
  • location_metapath: A Metapath for the node's location in the * document
  • *
  • value: Optional value signature if a value is present
  • *
- * The special characters ⪻ and ⪼ are used as delimiters to clearly separate the - * type from the location Metapath expression. + * The special characters \u2ABB and \u2ABC are used as delimiters to clearly + * separate the type from the location Metapath expression. * * @return the string signature of this node item */ + @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters") @Override public final String toSignature() { StringBuilder builder = new StringBuilder() .append(getType().toSignature()) - .append('⪻') + .append('\u2ABB') .append(getMetapath()) - .append('⪼'); + .append('\u2ABC'); String value = getValueSignature(); if (value != null) { builder.append('(') @@ -38,6 +42,11 @@ public final String toSignature() { return ObjectUtils.notNull(builder.toString()); } + /** + * Get the signature of this node's value. + * + * @return the value's signature or {@code null} if the node has no value + */ @Nullable protected abstract String getValueSignature(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAssemblyNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAssemblyNodeItem.java index 0ebdb645d..afde2540b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAssemblyNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IAssemblyNodeItem.java @@ -4,6 +4,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; import gov.nist.secauto.metaschema.core.metapath.function.InvalidTypeFunctionException; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; @@ -19,6 +20,11 @@ * A Metapath node valued item representing a Metaschema module assembly. */ public interface IAssemblyNodeItem extends IModelNodeItem { + /** + * Get the static type information of the node item. + * + * @return the item type + */ @NonNull static IItemType type() { return IItemType.assembly(); @@ -38,6 +44,11 @@ default NodeItemKind getNodeItemKind() { return NodeItemKind.ASSEMBLY; } + @Override + default NodeType getNodeType() { + return NodeType.ASSEMBLY; + } + @Override default IAssemblyNodeItem getNodeItem() { return this; @@ -64,4 +75,10 @@ default IAnyAtomicItem toAtomicItem() { default RESULT accept(@NonNull INodeItemVisitor visitor, CONTEXT context) { return visitor.visitAssembly(this, context); } + + @Override + default boolean deepEquals(ICollectionValue other) { + return other instanceof IAssemblyNodeItem + && NodeComparators.compareNodeItem(this, (IAssemblyNodeItem) other) == 0; + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IDocumentNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IDocumentNodeItem.java index f80d75c1d..f11aa38b3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IDocumentNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IDocumentNodeItem.java @@ -2,12 +2,22 @@ package gov.nist.secauto.metaschema.core.metapath.item.node; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; import edu.umd.cs.findbugs.annotations.NonNull; +/** + * A node item that represents the root of a tree of nodes associated with a + * document resource. + */ public interface IDocumentNodeItem extends IDocumentBasedNodeItem { + /** + * The node item's type. + * + * @return the type + */ @NonNull static IItemType type() { return IItemType.document(); @@ -24,6 +34,11 @@ default NodeItemKind getNodeItemKind() { return NodeItemKind.DOCUMENT; } + @Override + default NodeType getNodeType() { + return NodeType.DOCUMENT; + } + @Override default IDocumentNodeItem getNodeItem() { return this; @@ -46,4 +61,10 @@ default String format(@NonNull IPathFormatter formatter) { default RESULT accept(@NonNull INodeItemVisitor visitor, CONTEXT context) { return visitor.visitDocument(this, context); } + + @Override + default boolean deepEquals(ICollectionValue other) { + return other instanceof IDocumentNodeItem + && NodeComparators.compareNodeItem(this, (IDocumentNodeItem) other) == 0; + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureAtomicValuedItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureAtomicValuedItem.java index 14edf0439..89e132dbc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureAtomicValuedItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureAtomicValuedItem.java @@ -8,6 +8,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +// FIXME: cleanup these feature interfaces to reduce the number of interfaces and methods +// FIXME: rename to IFeatureRequiredDataAtomicValuedNodeItem interface IFeatureAtomicValuedItem extends IFeatureRequiredDataItem, IAtomicValuedItem { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureNoDataAtomicValuedItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureNoDataAtomicValuedItem.java index f4aa60f68..79702549d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureNoDataAtomicValuedItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFeatureNoDataAtomicValuedItem.java @@ -1,6 +1,7 @@ package gov.nist.secauto.metaschema.core.metapath.item.node; +import gov.nist.secauto.metaschema.core.metapath.function.InvalidTypeFunctionException; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; @@ -10,7 +11,6 @@ public interface IFeatureNoDataAtomicValuedItem extends IFeatureNoDataValuedItem @Override @Nullable default IAnyAtomicItem toAtomicItem() { - // no value - return null; + throw new InvalidTypeFunctionException(InvalidTypeFunctionException.DATA_ITEM_IS_FUNCTION, this); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java index 0e5aacfaa..9de685f98 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFieldNodeItem.java @@ -3,6 +3,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; @@ -20,11 +21,26 @@ public interface IFieldNodeItem extends IModelNodeItem, IAtomicValuedItem { + /** + * Get the static type information of the node item. + * + * @return the item type + */ + @NonNull + static IItemType type() { + return IItemType.field(); + } + @Override default NodeItemKind getNodeItemKind() { return NodeItemKind.FIELD; } + @Override + default NodeType getNodeType() { + return NodeType.FIELD; + } + @Override default IFieldNodeItem getNodeItem() { return this; @@ -48,7 +64,7 @@ default URI getBaseUri() { @Override default @NonNull - String format(@NonNull IPathFormatter formatter) { + String format(@NonNull IPathFormatter formatter) { return formatter.formatField(this); } @@ -56,4 +72,10 @@ String format(@NonNull IPathFormatter formatter) { default RESULT accept(@NonNull INodeItemVisitor visitor, CONTEXT context) { return visitor.visitField(this, context); } + + @Override + default boolean deepEquals(ICollectionValue other) { + return other instanceof IFieldNodeItem + && NodeComparators.compareNodeItem(this, (IFieldNodeItem) other) == 0; + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java index 325147e41..1003db254 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IFlagNodeItem.java @@ -3,6 +3,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAtomicValuedItem; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.metapath.type.IKindTest; @@ -25,11 +26,26 @@ public interface IFlagNodeItem extends IDefinitionNodeItem, IAtomicValuedItem { + /** + * Get the static type information of the node item. + * + * @return the item type + */ + @NonNull + static IItemType type() { + return IItemType.flag(); + } + @Override default NodeItemKind getNodeItemKind() { return NodeItemKind.FLAG; } + @Override + default NodeType getNodeType() { + return NodeType.FLAG; + } + @Override default IFlagNodeItem getNodeItem() { return this; @@ -84,7 +100,7 @@ default IFlagNodeItem getFlagByName(@NonNull IEnhancedQName name) { @SuppressWarnings("null") @Override default @NonNull - Stream flags() { + Stream flags() { // a flag does not have flags return Stream.empty(); } @@ -96,7 +112,7 @@ Stream flags() { @SuppressWarnings("null") @Override default @NonNull - Collection>> getModelItems() { + Collection>> getModelItems() { // a flag does not have model items return Collections.emptyList(); } @@ -125,7 +141,7 @@ Stream flags() { @Override default @NonNull - String format(@NonNull IPathFormatter formatter) { + String format(@NonNull IPathFormatter formatter) { return formatter.formatFlag(this); } @@ -133,4 +149,10 @@ String format(@NonNull IPathFormatter formatter) { default RESULT accept(@NonNull INodeItemVisitor visitor, CONTEXT context) { return visitor.visitFlag(this, context); } + + @Override + default boolean deepEquals(ICollectionValue other) { + return other instanceof IFlagNodeItem + && NodeComparators.compareNodeItem(this, (IFlagNodeItem) other) == 0; + } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IModuleNodeItem.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IModuleNodeItem.java index af4e033bf..6dcaa1c0f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IModuleNodeItem.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/node/IModuleNodeItem.java @@ -3,6 +3,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; +import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.type.IItemType; import gov.nist.secauto.metaschema.core.model.IModule; @@ -20,9 +21,19 @@ * be queried. */ public interface IModuleNodeItem extends IDocumentBasedNodeItem, IFeatureNoDataValuedItem { + /** + * Get the static type information of the node item. + * + * @return the item type + */ @NonNull static IItemType type() { - return IItemType.document(); + return IItemType.module(); + } + + @Override + default NodeType getNodeType() { + return NodeType.MODULE; } @Override @@ -67,4 +78,10 @@ default RESULT accept(@NonNull INodeItemVisitorMetapath + * specification. + */ +public final class NodeComparators { + private static final Comparator FLAG_SORT + = Comparator.comparing(IFlagNodeItem::getQName, IEnhancedQName::compareTo) + .thenComparing(IFlagNodeItem::toAtomicItem, IAnyAtomicItem::compareTo); + private static final Comparator NODE_ITEM + = Comparator.comparing(INodeItem::getNodeType) + .thenComparing(INodeItem::getFlags, NodeComparators::compareFlags) + .thenComparing(INodeItem::getModelItems, NodeComparators::compareModelItems); + private static final Comparator FIELD_NODE_ITEM + = Comparator.comparing(IFieldNodeItem::toAtomicItem, IAnyAtomicItem::compareTo); + + /** + * Compare two node items for equality. + * + * @param item1 + * the first item to compare + * @param item2 + * the second item to compare + * @return a negative integer, zero, or a positive integer if the first argument + * is less than, equal to, or greater than the second. + */ + public static int compareNodeItem(@NonNull INodeItem item1, @NonNull INodeItem item2) { + return NODE_ITEM.compare(item1, item2); + } + + /** + * Compare two node items for equality. + * + * @param item1 + * the first item to compare + * @param item2 + * the second item to compare + * @return a negative integer, zero, or a positive integer if the first argument + * is less than, equal to, or greater than the second. + */ + public static int compareNodeItem(@NonNull IFlagNodeItem item1, @NonNull IFlagNodeItem item2) { + return FLAG_SORT.compare(item1, item2); + } + + /** + * Compare two node items for equality. + * + * @param item1 + * the first item to compare + * @param item2 + * the second item to compare + * @return a negative integer, zero, or a positive integer if the first argument + * is less than, equal to, or greater than the second. + */ + public static int compareNodeItem(@NonNull IModelNodeItem item1, @NonNull IModelNodeItem item2) { + return getComparator(item1).compare(item1, item2); + } + + @SuppressWarnings("PMD.OnlyOneReturn") + private static int compareFlags( + @NonNull Collection flags1, + @NonNull Collection flags2) { + + Comparator> bySize = Comparator.comparingInt(Collection::size); + int delta = bySize.compare(flags1, flags2); + if (delta != 0) { + return delta; + } + + // sort the collections to compare in an order independent way + List list1 = new ArrayList<>(flags1); + List list2 = new ArrayList<>(flags2); + Collections.sort(list1, FLAG_SORT); + Collections.sort(list2, FLAG_SORT); + + // compare the results + for (int i = 0; i < list1.size(); i++) { + int compareResult = compareNodeItem( + ObjectUtils.requireNonNull(list1.get(i)), + ObjectUtils.requireNonNull(list2.get(i))); + if (compareResult != 0) { + return compareResult; + } + } + return 0; + } + + @SuppressWarnings("PMD.OnlyOneReturn") + private static int compareModelItems( + @NonNull Collection>> items1, + @NonNull Collection>> items2) { + + Comparator>>> bySize + = Comparator.comparingInt(Collection::size); + int delta = bySize.compare(items1, items2); + if (delta != 0) { + return delta; + } + + Iterator>> thisIterator = items1.iterator(); + Iterator>> otherIterator = items2.iterator(); + while (thisIterator.hasNext() && otherIterator.hasNext()) { + List> l1 = thisIterator.next(); + List> l2 = otherIterator.next(); + + Iterator> il1 = l1.iterator(); + Iterator> il2 = l2.iterator(); + while (thisIterator.hasNext() && otherIterator.hasNext()) { + IModelNodeItem item1 = ObjectUtils.requireNonNull(il1.next()); + IModelNodeItem item2 = ObjectUtils.requireNonNull(il2.next()); + + int result = compareNodeItem(item1, item2); + if (result != 0) { + return result; + } + } + } + return 0; + } + + @NonNull + private static Comparator> getComparator(@NonNull IModelNodeItem item) { + Comparator> retval; + if (item instanceof IAssemblyNodeItem) { + retval = NodeComparators::compareAsAssembly; + } else if (item instanceof IFieldNodeItem) { + retval = NodeComparators::compareAsField; + } else { + throw new UnsupportedOperationException("Unsupported model node item type: " + item.getClass().getName()); + } + return retval; + } + + @SuppressWarnings("PMD.OnlyOneReturn") + private static int compareAsField(@NonNull IModelNodeItem item1, @NonNull IModelNodeItem item2) { + int result = NODE_ITEM.compare(item1, item2); + if (result != 0) { + return result; + } + return FIELD_NODE_ITEM.compare((IFieldNodeItem) item1, (IFieldNodeItem) item2); + } + + private static int compareAsAssembly(@NonNull IModelNodeItem item1, @NonNull IModelNodeItem item2) { + return NODE_ITEM.compare(item1, item2); + } + + private NodeComparators() { + // disable construction + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/qname/IEnhancedQName.java b/core/src/main/java/gov/nist/secauto/metaschema/core/qname/IEnhancedQName.java index 968749035..22bfdced5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/qname/IEnhancedQName.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/qname/IEnhancedQName.java @@ -24,7 +24,7 @@ * memory footprint of qualified names and namespaces by reusing instances with * the same namespace and local name. */ -public interface IEnhancedQName { +public interface IEnhancedQName extends Comparable { /** * Get the index position of the qualified name. *

@@ -131,6 +131,16 @@ static IEnhancedQName of(@NonNull String namespace, @NonNull String localName) { return EQNameFactory.instance().newQName(namespace, localName); } + /** + * Generate a qualified name for this QName. + *

+ * This method uses prefixes associated with well-known namespaces, or will + * prepending the namespace if no prefix can be resolved. + * + * @param resolver + * the resolver to use to lookup the prefix + * @return the extended qualified-name + */ @NonNull default String toEQName() { return toEQName((NamespaceToPrefixResolver) null); @@ -154,6 +164,15 @@ default String toEQName(@Nullable NamespaceToPrefixResolver resolver) { return toEQName(namespace, getLocalName(), prefix); } + /** + * Generate a qualified name for this QName, use a prefix resolved from the + * provided static context, or by prepending the namespace if no prefix can be + * resolved. + * + * @param staticContext + * the static context to use to lookup the prefix + * @return the extended qualified-name + */ @NonNull default String toEQName(@NonNull StaticContext staticContext) { String namespace = getNamespace(); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/qname/QNameCache.java b/core/src/main/java/gov/nist/secauto/metaschema/core/qname/QNameCache.java index 8e2823743..5773af2e5 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/qname/QNameCache.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/qname/QNameCache.java @@ -8,6 +8,7 @@ import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.net.URI; +import java.util.Comparator; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -21,6 +22,10 @@ import nl.talsmasoftware.lazy4j.Lazy; final class QNameCache { + + private static final Comparator COMPARATOR + = Comparator.comparingInt(IEnhancedQName::getIndexPosition); + @NonNull private static final Lazy INSTANCE = ObjectUtils.notNull(Lazy.lazy(QNameCache::new)); @@ -157,9 +162,15 @@ public boolean equals(Object obj) { return Objects.equals(qnameIndexPosition, other.getIndexPosition()); } + @Override + public int compareTo(IEnhancedQName other) { + return COMPARATOR.compare(this, other); + } + @Override public String toString() { return toEQName(); } } + } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java index b3cbcc4e6..b8f56f7ad 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/util/CustomCollectors.java @@ -271,7 +271,7 @@ public static BinaryOperator useLastMapper() { @Override public Supplier> supplier() { - return ObjectUtils.notNull(ArrayList::new); + return ArrayList::new; } @Override diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqualTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqualTest.java new file mode 100644 index 000000000..74d73bc20 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnDeepEqualTest.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.bool; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnDeepEqualTest + extends ExpressionTestBase { + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + // FIXME: add tests for node items + Arguments.of( + bool(true), + "deep-equal(map{1:'a', 2:'b'}, map{2:'b', 1:'a'})"), + Arguments.of( + bool(true), + "deep-equal([1, 2, 3], [1, 2, 3])"), + Arguments.of( + bool(false), + "deep-equal((1, 2, 3), [1, 2, 3])"), + Arguments.of( + bool(false), + "deep-equal(1, current-dateTime())")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@NonNull IBooleanItem expected, @NonNull String metapath) { + assertEquals(expected, IMetapathExpression.compile(metapath) + .evaluateAs(null, IMetapathExpression.ResultType.ITEM, newDynamicContext())); + } +}