From 3818ef9ae9e23943e7a95f40ff0bbc0aa010556a Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Sat, 25 May 2024 10:53:52 -0400 Subject: [PATCH] Added support for the array:put Metapath function. Cleaned up some Javadocs. Added a utility function to convert a sequence into an array member. --- .../metaschema/core/metapath/ISequence.java | 16 +++ .../core/metapath/cst/ArraySquare.java | 16 +-- .../metapath/function/library/ArrayGet.java | 10 +- .../metapath/function/library/ArrayPut.java | 127 ++++++++++++++++++ .../library/DefaultFunctionLibrary.java | 2 + .../core/metapath/impl/ArrayItemN.java | 4 +- .../function/library/ArrayPutTest.java | 68 ++++++++++ 7 files changed, 222 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java create mode 100644 core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPutTest.java diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java index a0cd91f7d..521fbe465 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/ISequence.java @@ -153,6 +153,22 @@ default ITEM getFirstItem(boolean requireSingleton) { return getFirstItem(this, requireSingleton); } + @NonNull + default IItem toArrayMember() { + IItem retval; + switch (size()) { + case 0: + retval = IArrayItem.empty(); + break; + case 1: + retval = stream().findFirst().get(); + break; + default: + retval = IArrayItem.ofCollection(this); + } + return retval; + } + /** * Get a stream guaranteed to be backed by a list. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquare.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquare.java index 6a2e900a4..35dd54ef0 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquare.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/ArraySquare.java @@ -52,21 +52,7 @@ public List getChildren() { public ISequence accept(DynamicContext dynamicContext, ISequence focus) { return ISequence.of(getChildren().stream() .map(expr -> expr.accept(dynamicContext, focus)) - .map(sequence -> { - - IItem retval; - switch (sequence.size()) { - case 0: - retval = IArrayItem.empty(); - break; - case 1: - retval = sequence.iterator().next(); - break; - default: - retval = IArrayItem.ofCollection(sequence); - } - return retval; - }) + .map(ISequence::toArrayMember) .collect(IArrayItem.toArrayItem())); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java index 28661b499..26913f7ff 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayGet.java @@ -79,12 +79,14 @@ private static ISequence execute(@NonNull IFunction function, * "https://www.w3.org/TR/xpath-functions-31/#func-array-get">array:get. * * @param - * the type for the given Metapath sequence + * the type of items in the given Metapath array * @param target - * the sequence of Metapath items that is the target of insertion + * the array of Metapath items that is the target of retrieval * @param positionItem - * the integer position of the item to insert before - * @return the sequence of Metapath items with insertions + * the integer position of the item to retrieve + * @return the retrieved item + * @throws IndexOutOfBoundsException + * if the position is not in the range of 1 to array:size */ @SuppressWarnings("PMD.OnlyOneReturn") @NonNull diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java new file mode 100644 index 000000000..857102f6b --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPut.java @@ -0,0 +1,127 @@ +/* + * Portions of this software was developed by employees of the National Institute + * of Standards and Technology (NIST), an agency of the Federal Government and is + * being made available as a public service. Pursuant to title 17 United States + * Code Section 105, works of NIST employees are not subject to copyright + * protection in the United States. This software may be subject to foreign + * copyright. Permission in the United States and in foreign countries, to the + * extent that NIST may hold copyright, to use, copy, modify, create derivative + * works, and distribute this software and its documentation without fee is hereby + * granted on a non-exclusive basis, provided that this notice and disclaimer + * of warranty appears in all copies. + * + * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER + * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY + * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM + * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE + * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT + * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, + * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, + * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, + * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR + * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT + * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.ISequence; +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.atomic.IIntegerItem; +import gov.nist.secauto.metaschema.core.metapath.item.function.ArrayException; +import gov.nist.secauto.metaschema.core.metapath.item.function.IArrayItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class ArrayPut { + @NonNull + public static final IFunction SIGNATURE = IFunction.builder() + .name("put") + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS_ARRAY) + .argument(IArgument.builder() + .name("array") + .type(IArrayItem.class) + .one() + .build()) + .argument(IArgument.builder() + .name("position") + .type(IIntegerItem.class) + .one() + .build()) + .argument(IArgument.builder() + .name("member") + .type(IItem.class) + .zeroOrMore() + .build()) + .returnType(IArrayItem.class) + .returnOne() + .functionHandler(ArrayPut::execute) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence> execute(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + IArrayItem array = FunctionUtils.asType(ObjectUtils.requireNonNull( + arguments.get(0).getFirstItem(true))); + IIntegerItem position = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true))); + T member = FunctionUtils.asType(arguments.get(2).toArrayMember()); + + return ISequence.of(put(array, position, member)); + } + + /** + * An implementation of XPath 3.1 array:get. + * + * @param + * the type of items in the given Metapath array + * @param array + * the target Metapath array + * @param positionItem + * the integer position of the item to replace + * @param member + * the Metapath item to replace the identified array member with + * @return a new array containing the modification + * @throws IndexOutOfBoundsException + * if the position is not in the range of 1 to array:size + */ + @SuppressWarnings("PMD.OnlyOneReturn") + @NonNull + public static IArrayItem put( + @NonNull IArrayItem array, + @NonNull IIntegerItem positionItem, + @NonNull T member) { + return put(array, positionItem.asInteger().intValue(), member); + } + + @NonNull + public static IArrayItem put(@NonNull IArrayItem array, int position, @NonNull T member) { + + List copy = new ArrayList<>(array); + try { + copy.set(position - 1, member); + } catch (IndexOutOfBoundsException ex) { + throw new ArrayException( + ArrayException.INDEX_OUT_OF_BOUNDS, + String.format("The position %d is outside the range of values for the array of size '%d'.", + position, + copy.size()), + ex); + } + + return IArrayItem.ofCollection(copy); + } +} 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 b4ad85514..f6a89936c 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 @@ -197,6 +197,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional registerFunction(ArrayGet.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-array-size registerFunction(ArraySize.SIGNATURE); + // https://www.w3.org/TR/xpath-functions-31/#func-array-put + registerFunction(ArrayPut.SIGNATURE); // xpath casting functions registerFunction( diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java index b342c017d..518d930b8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/impl/ArrayItemN.java @@ -41,11 +41,11 @@ public class ArrayItemN @SafeVarargs public ArrayItemN(@NonNull ITEM... items) { - this(CollectionUtil.unmodifiableList(ObjectUtils.notNull(List.of(items)))); + this(ObjectUtils.notNull(List.of(items))); } public ArrayItemN(@NonNull List items) { - this.items = items; + this.items = CollectionUtil.unmodifiableList(items); } @Override diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPutTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPutTest.java new file mode 100644 index 000000000..b9b323172 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/ArrayPutTest.java @@ -0,0 +1,68 @@ +/* + * Portions of this software was developed by employees of the National Institute + * of Standards and Technology (NIST), an agency of the Federal Government and is + * being made available as a public service. Pursuant to title 17 United States + * Code Section 105, works of NIST employees are not subject to copyright + * protection in the United States. This software may be subject to foreign + * copyright. Permission in the United States and in foreign countries, to the + * extent that NIST may hold copyright, to use, copy, modify, create derivative + * works, and distribute this software and its documentation without fee is hereby + * granted on a non-exclusive basis, provided that this notice and disclaimer + * of warranty appears in all copies. + * + * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER + * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY + * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM + * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE + * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT + * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, + * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, + * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, + * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR + * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT + * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.array; +import static gov.nist.secauto.metaschema.core.metapath.TestUtils.string; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; + +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 ArrayPutTest + extends ExpressionTestBase { + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + array(string("a"), string("d"), string("c")), + "array:put([\"a\", \"b\", \"c\"], 2, \"d\")"), + Arguments.of( + array(string("a"), array(string("d"), string("e")), string("c")), + "array:put([\"a\", \"b\", \"c\"], 2, (\"d\", \"e\"))"), + Arguments.of( + array(array(string("d"), string("e"))), + "array:put([\"a\"], 1, [\"d\", \"e\"]) ")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void testExpression(@NonNull IItem expected, @NonNull String metapath) { + + IItem result = MetapathExpression.compile(metapath) + .evaluateAs(null, MetapathExpression.ResultType.NODE, newDynamicContext()); + assertEquals(expected, result); + } +}