diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionLibrary.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionLibrary.java index e4518928e..32890d7f8 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionLibrary.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/FunctionLibrary.java @@ -120,6 +120,7 @@ public IFunction getFunction(@NonNull QName name, int arity) { private static class NamedFunctionSet { private final Map arityToFunctionMap; + private IFunction unboundedArity; public NamedFunctionSet() { this.arityToFunctionMap = new HashMap<>(); @@ -133,12 +134,20 @@ public Stream getFunctionsAsStream() { @Nullable public IFunction getFunctionWithArity(int arity) { - return arityToFunctionMap.get(arity); + IFunction retval = arityToFunctionMap.get(arity); + if (retval == null && unboundedArity != null && unboundedArity.arity() < arity) { + retval = unboundedArity; + } + return retval; } @Nullable public IFunction addFunction(@NonNull IFunction function) { - return arityToFunctionMap.put(function.arity(), function); + IFunction old = arityToFunctionMap.put(function.arity(), function); + if (function.isArityUnbounded()) { + unboundedArity = function; + } + return old; } } } 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 93b88afec..21fd664bc 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 @@ -68,6 +68,7 @@ public DefaultFunctionLibrary() { // NOPMD - intentional registerFunction(FnCeiling.SIGNATURE); // https://www.w3.org/TR/xpath-functions-31/#func-compare registerFunction(FnCompare.SIGNATURE); + registerFunction(FnConcat.SIGNATURE); // P1: https://www.w3.org/TR/xpath-functions-31/#func-concat // P1: https://www.w3.org/TR/xpath-functions-31/#func-contains // https://www.w3.org/TR/xpath-functions-31/#func-count diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnCompare.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnCompare.java index a4be84cfe..3c834989c 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnCompare.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnCompare.java @@ -41,7 +41,11 @@ import edu.umd.cs.findbugs.annotations.NonNull; -public class FnCompare { +public final class FnCompare { + + private FnCompare() { + // disable construction + } @NonNull static final IFunction SIGNATURE = IFunction.builder() diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcat.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcat.java new file mode 100644 index 000000000..c72a037df --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcat.java @@ -0,0 +1,107 @@ +/* + * 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.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public final class FnConcat { + @NonNull + static final IFunction SIGNATURE = IFunction.builder() + .name("concat") + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("arg1") + .type(IAnyAtomicItem.class) + .zeroOrOne() + .build()) + .argument(IArgument.builder() + .name("arg2") + .type(IAnyAtomicItem.class) + .zeroOrOne() + .build()) + .allowUnboundedArity(true) + .returnType(IStringItem.class) + .returnOne() + .functionHandler(FnConcat::execute) + .build(); + + private FnConcat() { + // disable construction + } + + @NonNull + private static ISequence execute( + @NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + + return ISequence.of(concat(ObjectUtils.notNull(arguments.stream() + .map(arg -> { + assert arg != null; + return (IAnyAtomicItem) FunctionUtils.getFirstItem(arg, true); + })))); + } + + @NonNull + public static IStringItem concat(IAnyAtomicItem... items) { + return concat(ObjectUtils.notNull(Arrays.asList(items))); + } + + @NonNull + public static IStringItem concat(@NonNull List items) { + return concat(ObjectUtils.notNull(items.stream())); + } + + @NonNull + public static IStringItem concat(@NonNull Stream items) { + return IStringItem.valueOf(ObjectUtils.notNull(items + .map(item -> { + return item == null ? "" : IStringItem.cast(item).asString(); + }) + .collect(Collectors.joining()))); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnData.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnData.java index 665df22ec..55c72abd4 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnData.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnData.java @@ -104,7 +104,8 @@ private static ISequence executeNoArg(@NonNull IFunction functio @SuppressWarnings("unused") @NonNull - private static ISequence executeOneArg(@NonNull IFunction function, + private static ISequence executeOneArg( + @NonNull IFunction function, @NonNull List> arguments, @NonNull DynamicContext dynamicContext, IItem focus) { diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcatTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcatTest.java new file mode 100644 index 000000000..8782b2216 --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnConcatTest.java @@ -0,0 +1,77 @@ +/* + * 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.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; + +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 FnConcatTest + extends ExpressionTestBase { + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + ISequence.of(string("ungrateful")), + "concat('un','grateful')"), + Arguments.of( + ISequence.of(string("Thy old groans ring yet in my ancient ears.")), + "concat('Thy ', (), 'old ', \"groans\", \"\", ' ring', ' yet', ' in', ' my', ' ancient',' ears.')"), + Arguments.of( + ISequence.of(string("Ciao!")), + "concat('Ciao!',())"), + Arguments.of( + ISequence.of(string("Ingratitude, thou marble-hearted fiend!")), + "concat('Ingratitude, ', 'thou ', 'marble-hearted', ' fiend!')"), + Arguments.of( + ISequence.of(string("1234true")), + "concat(01, 02, 03, 04, true())"), + Arguments.of( + ISequence.of(string("10/6")), + "10 || '/' || 6")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void testExpression(@NonNull ISequence expected, @NonNull String metapath) { + assertEquals( + expected, + MetapathExpression.compile(metapath) + .evaluateAs(null, MetapathExpression.ResultType.SEQUENCE, newDynamicContext())); + } + +}