From 0804a78281c55537ac69ff6a9f23b3e31bb9f863 Mon Sep 17 00:00:00 2001 From: David Waltermire Date: Tue, 26 Nov 2024 18:40:16 -0500 Subject: [PATCH] Completed testing eqname and varref cases for the arrow operator. --- .../core/metapath/StaticContext.java | 1 + .../cst/AbstractExpressionVisitor.java | 7 +- .../core/metapath/cst/BuildCSTVisitor.java | 37 ++++++-- .../core/metapath/cst/CSTPrinter.java | 9 +- .../metapath/cst/DynamicFunctionCall.java | 84 +++++++++++++++++++ .../core/metapath/cst/IExpressionVisitor.java | 13 ++- .../core/metapath/cst/StaticFunctionCall.java | 24 ++++-- .../library/DefaultFunctionLibrary.java | 1 - .../item/atomic/AbstractAtomicItemBase.java | 4 +- .../metapath/item/atomic/IAnyAtomicItem.java | 1 + .../metapath/item/node/AbstractNodeItem.java | 10 +-- .../metapath/cst/ArrowExpressionTest.java | 25 ++++-- .../function/library/CastFunctionTest.java | 4 + pom.xml | 7 ++ 14 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DynamicFunctionCall.java diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java index c49412a3b..45ab73d3f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/StaticContext.java @@ -39,6 +39,7 @@ * The implementation of a Metapath * static context. */ +// FIXME: refactor well-known into a new class public final class StaticContext { @NonNull private static final Map WELL_KNOWN_NAMESPACES; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java index 43a21acce..e1f611642 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractExpressionVisitor.java @@ -210,7 +210,12 @@ public RESULT visitFlag(Flag expr, CONTEXT context) { } @Override - public RESULT visitFunctionCall(StaticFunctionCall expr, CONTEXT context) { + public RESULT visitStaticFunctionCall(StaticFunctionCall expr, CONTEXT context) { + return visitChildren(expr, context); + } + + @Override + public RESULT visitDynamicFunctionCall(DynamicFunctionCall expr, CONTEXT context) { return visitChildren(expr, context); } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java index 304d61246..99a9c7786 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/BuildCSTVisitor.java @@ -1154,16 +1154,12 @@ protected IExpression handleSimplemapexpr(Metapath10.SimplemapexprContext contex @Override protected IExpression handleArrowexpr(Metapath10.ArrowexprContext context) { - // FIXME: handle additional syntax for varef and parenthesized - return handleGroupedNAiry(context, 0, 3, (ctx, idx, left) -> { // the next child is "=>" assert "=>".equals(ctx.getChild(idx).getText()); int offset = (idx - 1) / 3; - Metapath10.ArrowfunctionspecifierContext fcCtx - = ctx.getChild(Metapath10.ArrowfunctionspecifierContext.class, offset); Metapath10.ArgumentlistContext argumentCtx = ctx.getChild(Metapath10.ArgumentlistContext.class, offset); try (Stream args = Stream.concat( @@ -1171,12 +1167,35 @@ protected IExpression handleArrowexpr(Metapath10.ArrowexprContext context) { parseArgumentList(ObjectUtils.notNull(argumentCtx)))) { assert args != null; - List arguments = ObjectUtils.notNull(args.collect(Collectors.toUnmodifiableList())); + // prepend the focus + List arguments = ObjectUtils.notNull(args + .collect(Collectors.toUnmodifiableList())); + + Metapath10.ArrowfunctionspecifierContext arrowCtx + = ctx.getChild(Metapath10.ArrowfunctionspecifierContext.class, offset); + if (arrowCtx.eqname() != null) { + // named function + return new StaticFunctionCall( + () -> getContext().lookupFunction(ObjectUtils.notNull(arrowCtx.eqname().getText()), arguments.size()), + arguments); + } + + IExpression result; + if (arrowCtx.varref() != null) { + // function instance or name reference + result = new VariableReference(getContext().parseVariableName( + ObjectUtils.notNull(arrowCtx.varref().varname().eqname().getText()))); + } else if (arrowCtx.parenthesizedexpr() != null) { + // function expression + result = visit(arrowCtx.parenthesizedexpr().expr()); + } else { + throw new StaticMetapathException( + StaticMetapathException.INVALID_PATH_GRAMMAR, + String.format("Unable to get function name using arrow specifier '%s'.", arrowCtx.getText())); + } - return new StaticFunctionCall( - () -> getContext().lookupFunction( - ObjectUtils.notNull(fcCtx.eqname().getText()), - arguments.size()), + return new DynamicFunctionCall( + result, arguments); } }); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java index e4ec93920..61bd756c7 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/CSTPrinter.java @@ -192,8 +192,13 @@ public String visitFlag(Flag expr, State context) { } @Override - public String visitFunctionCall(StaticFunctionCall expr, State context) { - return appendNode(expr, super.visitFunctionCall(expr, context), context); + public String visitStaticFunctionCall(StaticFunctionCall expr, State context) { + return appendNode(expr, super.visitStaticFunctionCall(expr, context), context); + } + + @Override + public String visitDynamicFunctionCall(DynamicFunctionCall expr, State context) { + return appendNode(expr, super.visitDynamicFunctionCall(expr, context), context); } @Override diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DynamicFunctionCall.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DynamicFunctionCall.java new file mode 100644 index 000000000..db4ac8570 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/DynamicFunctionCall.java @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.cst; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Executes a function call based on a specifier expression that is used to + * dtermine the function and multiple argument expressions that are used to + * determine the function arguments. + */ +public class DynamicFunctionCall implements IExpression { + @NonNull + private final IExpression functionIdentifier; + @NonNull + private final List arguments; + + /** + * Construct a new function call expression. + * + * @param functionIdentifier + * the function expression, identifying either a function or function + * name + * @param arguments + * the expressions used to provide arguments to the function call + */ + public DynamicFunctionCall(@NonNull IExpression functionIdentifier, @NonNull List arguments) { + this.functionIdentifier = functionIdentifier; + this.arguments = arguments; + } + + @Override + public List getChildren() { + return ObjectUtils.notNull(Stream.concat( + Stream.of(functionIdentifier), + arguments.stream()) + .collect(Collectors.toUnmodifiableList())); + } + + @Override + public Class getBaseResultType() { + return IItem.class; + } + + @Override + public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { + return visitor.visitDynamicFunctionCall(this, context); + } + + @Override + public ISequence accept(DynamicContext dynamicContext, ISequence focus) { + List> arguments = ObjectUtils.notNull(this.arguments.stream() + .map(expression -> expression.accept(dynamicContext, focus)).collect(Collectors.toList())); + + IItem specifier = functionIdentifier.accept(dynamicContext, focus).getFirstItem(true); + IFunction function; + if (specifier instanceof IFunction) { + function = (IFunction) specifier; + } else if (specifier != null) { + function = dynamicContext.getStaticContext().lookupFunction( + specifier.toAtomicItem().asString(), + arguments.size()); + } else { + throw new StaticMetapathException( + StaticMetapathException.NO_FUNCTION_MATCH, + "Unable to get function name. The error specifier is an empty sequence."); + } + return function.execute(arguments, dynamicContext, focus); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java index e8bbe6243..b03732178 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/IExpressionVisitor.java @@ -197,7 +197,18 @@ public interface IExpressionVisitor { * the processing context * @return the visitation result or {@code null} if no result was produced */ - RESULT visitFunctionCall(@NonNull StaticFunctionCall expr, @NonNull CONTEXT context); + RESULT visitStaticFunctionCall(@NonNull StaticFunctionCall expr, @NonNull CONTEXT context); + + /** + * Visit the CST node. + * + * @param expr + * the CST node to visit + * @param context + * the processing context + * @return the visitation result or {@code null} if no result was produced + */ + RESULT visitDynamicFunctionCall(@NonNull DynamicFunctionCall expr, @NonNull CONTEXT context); /** * Visit the CST node. 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 fc329c615..1e8e77024 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 @@ -19,23 +19,30 @@ import edu.umd.cs.findbugs.annotations.NonNull; import nl.talsmasoftware.lazy4j.Lazy; +/** + * Executes a function call based on the provided function and multiple argument + * expressions that are used to determine the function arguments. + */ + public class StaticFunctionCall implements IExpression { - @NonNull - private final List arguments; @NonNull private final Lazy functionSupplier; + @NonNull + private final List arguments; /** * Construct a new function call expression. * * @param functionSupplier - * the function implementation supplier + * the function supplier * @param arguments * the expressions used to provide arguments to the function call */ public StaticFunctionCall(@NonNull Supplier functionSupplier, @NonNull List arguments) { - this.arguments = arguments; + // lazy fetches the function so that Metapaths can parse even if a function does + // not exist this.functionSupplier = ObjectUtils.notNull(Lazy.lazy(functionSupplier)); + this.arguments = arguments; } /** @@ -47,7 +54,7 @@ public StaticFunctionCall(@NonNull Supplier functionSupplier, @NonNul * if the function was not found */ public IFunction getFunction() { - return ObjectUtils.notNull(functionSupplier.get()); + return functionSupplier.get(); } @Override @@ -63,17 +70,18 @@ public Class getBaseResultType() { @SuppressWarnings("null") @Override public String toASTString() { - return String.format("%s[name=%s]", getClass().getName(), getFunction().getQName()); + return String.format("%s[name=%s, arity=%d]", getClass().getName(), getFunction().getQName(), + getFunction().arity()); } @Override public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { - return visitor.visitFunctionCall(this, context); + return visitor.visitStaticFunctionCall(this, context); } @Override public ISequence accept(DynamicContext dynamicContext, ISequence focus) { - List> arguments = ObjectUtils.notNull(getChildren().stream() + List> arguments = ObjectUtils.notNull(this.arguments.stream() .map(expression -> expression.accept(dynamicContext, focus)).collect(Collectors.toList())); IFunction function = getFunction(); 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 4c3fb8eff..4c4e7b25f 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 @@ -18,7 +18,6 @@ * function * specification. */ -@SuppressWarnings({ "removal" }) @SuppressFBWarnings("UWF_UNWRITTEN_FIELD") public class DefaultFunctionLibrary extends FunctionLibrary { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractAtomicItemBase.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractAtomicItemBase.java index 3333dc640..4f7baa05a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractAtomicItemBase.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/AbstractAtomicItemBase.java @@ -31,9 +31,9 @@ public String asString() { public String toSignature() { return ObjectUtils.notNull(new StringBuilder() .append(getType().toSignature()) - .append("(") + .append('(') .append(getValueSignature()) - .append(")") + .append(')') .toString()); } 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 219e080a3..23f030863 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 @@ -55,6 +55,7 @@ default IAnyAtomicItem toAtomicItem() { * * @return a new string item */ + @NonNull default IStringItem asStringItem() { return IStringItem.valueOf(asString()); } 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 7d6043af6..060470b8e 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 @@ -11,14 +11,14 @@ public abstract class AbstractNodeItem implements INodeItem { public final String toSignature() { StringBuilder builder = new StringBuilder() .append(getType().toSignature()) - .append("⪻") + .append('⪻') .append(getMetapath()) - .append("⪼"); + .append('⪼'); String value = getValueSignature(); if (value != null) { - builder.append("("); - builder.append(value); - builder.append(")"); + builder.append('(') + .append(value) + .append(')'); } return ObjectUtils.notNull(builder.toString()); } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/ArrowExpressionTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/ArrowExpressionTest.java index 64f354a3e..60aa88584 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/ArrowExpressionTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/ArrowExpressionTest.java @@ -6,12 +6,15 @@ package gov.nist.secauto.metaschema.core.metapath.cst; import static gov.nist.secauto.metaschema.core.metapath.TestUtils.bool; -import static gov.nist.secauto.metaschema.core.metapath.TestUtils.integer; +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.DynamicContext; 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 gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -23,19 +26,31 @@ class ArrowExpressionTest extends ExpressionTestBase { + private static final String NS = "http://example.com/ns"; + private static Stream provideValues() { // NOPMD - false positive return Stream.of( // Arguments.of(ISequence.of(string("true")), "true() => string()"), - Arguments.of(ISequence.of(bool(false)), "() => exists()"), - Arguments.of(ISequence.of(integer(3)), "(1, 2) => sum()")); + Arguments.of(ISequence.of(string("ABC")), "'abc' => upper-case()"), + Arguments.of(ISequence.of(string("123")), "'1' => concat('2') => concat('3')"), + Arguments.of(ISequence.of(bool(true)), "() => $ex:var1()")); } @ParameterizedTest @MethodSource("provideValues") void testArrowExpression(@NonNull ISequence expected, @NonNull String metapath) { + StaticContext staticContext = StaticContext.builder() + .namespace("ex", NS) + .build(); + DynamicContext dynamicContext = new DynamicContext(staticContext); + dynamicContext.bindVariableValue(IEnhancedQName.of(NS, "var1"), ISequence.of(string("fn:empty"))); + assertEquals( expected, - MetapathExpression.compile(metapath).evaluateAs(null, MetapathExpression.ResultType.SEQUENCE, - newDynamicContext())); + MetapathExpression.compile(metapath, staticContext) + .evaluateAs( + null, + MetapathExpression.ResultType.SEQUENCE, + dynamicContext)); } } diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/CastFunctionTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/CastFunctionTest.java index 46c7b601b..4231f296b 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/CastFunctionTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/CastFunctionTest.java @@ -1,3 +1,7 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ package gov.nist.secauto.metaschema.core.metapath.function.library; diff --git a/pom.xml b/pom.xml index 3f82ea8e0..6f8373db7 100644 --- a/pom.xml +++ b/pom.xml @@ -630,6 +630,13 @@ false + + org.apache.maven.plugins + maven-release-plugin + + SemVerVersionPolicy + + org.apache.maven.plugins maven-deploy-plugin