diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java index 1a4e88a73..273e537fa 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/DynamicContext.java @@ -10,7 +10,7 @@ import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration; import gov.nist.secauto.metaschema.core.configuration.IConfiguration; import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration; -import gov.nist.secauto.metaschema.core.metapath.function.DefaultFunction.CallingContext; +import gov.nist.secauto.metaschema.core.metapath.function.CallingContext; import gov.nist.secauto.metaschema.core.metapath.function.IFunction.FunctionProperty; import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; import gov.nist.secauto.metaschema.core.model.IUriResolver; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java index a61c80eab..74eec5120 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/antlr/AbstractAstVisitor.java @@ -246,6 +246,40 @@ public R visitArgument(Metapath10.ArgumentContext ctx) { throw new IllegalStateException(ERR_NO_DELEGATION); } + // ============================================================ + // https://www.w3.org/TR/xpath-31/#doc-xpath31-NamedFunctionRef + // ============================================================ + + @Override + public R visitNamedfunctionref(Metapath10.NamedfunctionrefContext ctx) { + throw new UnsupportedOperationException("expression not supported"); + } + + // ============================================== + // https://www.w3.org/TR/xpath-31/#id-inline-func + // ============================================== + + @Override + public R visitFunctionitemexpr(Metapath10.FunctionitemexprContext ctx) { + assert ctx != null; + return delegateToChild(ctx); + } + + /** + * Handle the provided expression. + * + * @param ctx + * the provided expression context + * @return the result + */ + protected abstract R handleInlinefunctionexpr(@NonNull Metapath10.InlinefunctionexprContext ctx); + + @Override + public R visitInlinefunctionexpr(Metapath10.InlinefunctionexprContext ctx) { + assert ctx != null; + return handle(ctx, this::handleInlinefunctionexpr); + } + // ======================================================================= // Enclosed Expressions - https://www.w3.org/TR/xpath-31/#id-enclosed-expr // ======================================================================= @@ -1018,16 +1052,6 @@ public R visitArrowfunctionspecifier(Metapath10.ArrowfunctionspecifierContext ct throw new IllegalStateException(ERR_NO_DELEGATION); } - @Override - public R visitNamedfunctionref(Metapath10.NamedfunctionrefContext ctx) { - throw new UnsupportedOperationException("expression not supported"); - } - - @Override - public R visitInlinefunctionexpr(Metapath10.InlinefunctionexprContext ctx) { - throw new UnsupportedOperationException("expression not supported"); - } - /* * ========================================================== * The following are handled inline by other expression types @@ -1225,12 +1249,6 @@ public R visitFunctionbody(Metapath10.FunctionbodyContext ctx) { throw new IllegalStateException(ERR_NO_DELEGATION); } - @Override - public R visitFunctionitemexpr(Metapath10.FunctionitemexprContext ctx) { - // should never be called, since this is handled by the parent expression - throw new IllegalStateException(ERR_NO_DELEGATION); - } - @Override public R visitTypedeclaration(Metapath10.TypedeclarationContext ctx) { // should never be called, since this is handled by the parent expression diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java index ef2124bec..56fef7d38 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AbstractCSTVisitorBase.java @@ -135,7 +135,48 @@ public IExpression visit(ParseTree tree) { * @return the outer expression or {@code null} if no children exist to parse */ @Nullable - protected + protected > + List nairyToList( + @NonNull CONTEXT context, + int startIndex, + int step, + @NonNull BiFunction parser) { + int numChildren = context.getChildCount(); + + List retval = null; + if (startIndex < numChildren) { + retval = new ArrayList<>((numChildren - startIndex) / step); + for (int idx = startIndex; idx < numChildren; idx += step) { + R result = parser.apply(context, idx); + retval.add(result); + } + } + return retval; + } + + /** + * Parse the provided context as an n-ary phrase. + * + * @param + * the Java type of the antlr context to parse + * @param + * the Java type of the child expressions produced by this parser + * @param + * the Java type of the outer expression produced by the parser + * @param context + * the antlr context to parse + * @param startIndex + * the child index to start parsing on + * @param step + * the increment to advance while parsing child expressions + * @param parser + * a binary function used to produce child expressions + * @param supplier + * a function used to produce the other expression + * @return the outer expression or {@code null} if no children exist to parse + */ + @Nullable + protected R nairyToCollection( @NonNull CONTEXT context, int startIndex, 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 e1f611642..ae50899f3 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 @@ -219,6 +219,11 @@ public RESULT visitDynamicFunctionCall(DynamicFunctionCall expr, CONTEXT context return visitChildren(expr, context); } + @Override + public RESULT visitAnonymousFunctionCall(AnonymousFunctionCall expr, CONTEXT context) { + return visitChildren(expr, context); + } + @Override public RESULT visitIntegerDivision(IntegerDivision expr, CONTEXT context) { return visitChildren(expr, context); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCall.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCall.java new file mode 100644 index 000000000..7b9e7209e --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCall.java @@ -0,0 +1,116 @@ +/* + * 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.function.IArgument; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.function.impl.AbstractFunction; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * 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 AnonymousFunctionCall + extends AbstractFunction + implements IExpression, IFunction { + @NonNull + private static final Set PROPERTIES = ObjectUtils.notNull(EnumSet.of( + // FunctionProperty.CONTEXT_DEPENDENT, + // FunctionProperty.FOCUS_DEPENDENT, + FunctionProperty.DETERMINISTIC)); + @NonNull + private final ISequenceType result; + @NonNull + private final IExpression body; + + /** + * Construct a new function call expression. + * + * @param arguments + */ + public AnonymousFunctionCall( + @NonNull List arguments, + @NonNull ISequenceType result, + @NonNull IExpression body) { + super("(anonymous)-" + UUID.randomUUID().toString(), "", arguments); + this.result = result; + this.body = body; + } + + @Override + public List getChildren() { + return ObjectUtils.notNull(List.of(body)); + } + + @Override + public Class getBaseResultType() { + return IFunction.class; + } + + @Override + public RESULT accept(IExpressionVisitor visitor, CONTEXT context) { + return visitor.visitAnonymousFunctionCall(this, context); + } + + @Override + public ISequence accept(DynamicContext dynamicContext, ISequence focus) { + return ISequence.of(this); + } + + @SuppressWarnings("null") + @Override + public String toASTString() { + return String.format("%s[arguments=%s,return=%s]", + getClass().getName(), getName(), + getArguments(), + result.toSignature()); + } + + @Override + public Set getProperties() { + return PROPERTIES; + } + + @Override + public ISequenceType getResult() { + return result; + } + + @Override + @NonNull + protected ISequence executeInternal( + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + @Nullable IItem focus) { + + DynamicContext subContext = dynamicContext.subContext(); + Iterator> args = arguments.iterator(); + Iterator params = getArguments().iterator(); + while (args.hasNext() && params.hasNext()) { + ISequence sequence = args.next(); + IArgument param = params.next(); + + subContext.bindVariableValue(param.getName(), ObjectUtils.notNull(sequence)); + } + + return body.accept(subContext, ISequence.of(focus)); + } +} 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 99a9c7786..a4afff8cd 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 @@ -8,6 +8,7 @@ import gov.nist.secauto.metaschema.core.metapath.StaticContext; import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10; +import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10.ParamContext; import gov.nist.secauto.metaschema.core.metapath.antlr.Metapath10Lexer; import gov.nist.secauto.metaschema.core.metapath.cst.items.ArraySequenceConstructor; import gov.nist.secauto.metaschema.core.metapath.cst.items.ArraySquareConstructor; @@ -61,12 +62,14 @@ import gov.nist.secauto.metaschema.core.metapath.cst.type.Treat; import gov.nist.secauto.metaschema.core.metapath.cst.type.TypeTestSupport; import gov.nist.secauto.metaschema.core.metapath.function.ComparisonFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; import gov.nist.secauto.metaschema.core.metapath.impl.AbstractKeySpecifier; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; import gov.nist.secauto.metaschema.core.metapath.item.function.IKeySpecifier; 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.ISequenceType; +import gov.nist.secauto.metaschema.core.metapath.type.Occurrence; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.CollectionUtil; import gov.nist.secauto.metaschema.core.util.ObjectUtils; @@ -100,6 +103,9 @@ // https://www.w3.org/TR/xpath-31/#id-node-comparisons public class BuildCSTVisitor extends AbstractCSTVisitorBase { + private static final ISequenceType DEFAULT_FUNCTION_SEQUENCE_TYPE + = ISequenceType.of(IItemType.item(), Occurrence.ZERO_OR_MORE); + @NonNull private final StaticContext context; @@ -381,6 +387,51 @@ protected IExpression handleFunctioncall(Metapath10.FunctioncallContext ctx) { arguments); } + // ============================================================ + // https://www.w3.org/TR/xpath-31/#doc-xpath31-NamedFunctionRef + // ============================================================ + + @Override + public IExpression visitNamedfunctionref(Metapath10.NamedfunctionrefContext ctx) { + throw new UnsupportedOperationException("expression not supported"); + } + + // ============================================== + // https://www.w3.org/TR/xpath-31/#id-inline-func + // ============================================== + + @Override + public IExpression handleInlinefunctionexpr(Metapath10.InlinefunctionexprContext context) { + // parse the param list + List parameters = ObjectUtils.notNull(context.paramlist() == null + ? CollectionUtil.emptyList() + : nairyToList( + ObjectUtils.notNull(context.paramlist()), + 0, + 2, + (ctx, idx) -> { + int pos = (idx - 1) / 2; + ParamContext tree = ctx.param(pos); + return IArgument.of( + getContext().parseVariableName(ObjectUtils.notNull(tree.eqname().getText())), + tree.typedeclaration() == null + ? DEFAULT_FUNCTION_SEQUENCE_TYPE + : TypeTestSupport.parseSequenceType(tree.typedeclaration().sequencetype(), getContext())); + })); + + // parse the result type + ISequenceType resultSequenceType = context.sequencetype() == null + ? DEFAULT_FUNCTION_SEQUENCE_TYPE + : TypeTestSupport.parseSequenceType( + ObjectUtils.notNull(context.sequencetype()), + getContext()); + + // parse the function body + IExpression body = visit(context.functionbody().enclosedexpr()); + + return new AnonymousFunctionCall(parameters, resultSequenceType, body); + } + // ========================================================================= // Filter Expressions - https://www.w3.org/TR/xpath-31/#id-filter-expression // ========================================================================= @@ -448,7 +499,8 @@ protected IExpression handlePostfixexpr(Metapath10.PostfixexprContext context) { // map or array access using function call syntax result = new FunctionCallAccessor( left, - ObjectUtils.notNull(parseArgumentList((Metapath10.ArgumentlistContext) tree).findFirst().get())); + ObjectUtils.notNull(parseArgumentList((Metapath10.ArgumentlistContext) tree) + .collect(Collectors.toUnmodifiableList()))); } else if (tree instanceof Metapath10.PredicateContext) { result = new PredicateExpression( left, 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 61bd756c7..01b9dd2da 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 @@ -201,6 +201,11 @@ public String visitDynamicFunctionCall(DynamicFunctionCall expr, State context) return appendNode(expr, super.visitDynamicFunctionCall(expr, context), context); } + @Override + public String visitAnonymousFunctionCall(AnonymousFunctionCall expr, State context) { + return appendNode(expr, super.visitAnonymousFunctionCall(expr, context), context); + } + @Override public String visitIntegerDivision(IntegerDivision expr, State context) { return appendNode(expr, super.visitIntegerDivision(expr, context), context); 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 b03732178..2ccf58ba3 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 @@ -210,6 +210,17 @@ public interface IExpressionVisitor { */ RESULT visitDynamicFunctionCall(@NonNull DynamicFunctionCall 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 visitAnonymousFunctionCall(@NonNull AnonymousFunctionCall expr, @NonNull CONTEXT context); + /** * Visit the CST node. * diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/items/FunctionCallAccessor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/items/FunctionCallAccessor.java index 5e0fbcd62..76b5e659a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/items/FunctionCallAccessor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/items/FunctionCallAccessor.java @@ -9,6 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.ICollectionValue; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; +import gov.nist.secauto.metaschema.core.metapath.cst.AnonymousFunctionCall; import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.library.ArrayGet; @@ -20,6 +21,8 @@ import gov.nist.secauto.metaschema.core.metapath.item.function.IMapItem; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; @@ -27,7 +30,7 @@ public class FunctionCallAccessor implements IExpression { @NonNull private final IExpression base; @NonNull - private final IExpression argument; + private final List arguments; /** * Construct a new functional call accessor. @@ -39,9 +42,9 @@ public class FunctionCallAccessor implements IExpression { * the value to find, which will be the key for a map or the index for * an array */ - public FunctionCallAccessor(@NonNull IExpression base, @NonNull IExpression keyOrIndex) { + public FunctionCallAccessor(@NonNull IExpression base, @NonNull List arguments) { this.base = base; - this.argument = keyOrIndex; + this.arguments = arguments; } /** @@ -60,21 +63,35 @@ public IExpression getBase() { * @return the argument */ @NonNull - public IExpression getArgument() { - return argument; + public List getArguments() { + return arguments; } @SuppressWarnings("null") @Override - public List getChildren() { - return List.of(getBase(), getArgument()); + public List getChildren() { + return Stream.concat(Stream.of(getBase()), getArguments().stream()) + .collect(Collectors.toUnmodifiableList()); } @Override public ISequence accept(DynamicContext dynamicContext, ISequence focus) { ISequence target = getBase().accept(dynamicContext, focus); IItem collection = target.getFirstItem(true); - IAnyAtomicItem key = ISequence.of(getArgument().accept(dynamicContext, focus).atomize()) + + if (collection instanceof AnonymousFunctionCall) { + return ((AnonymousFunctionCall) collection).execute( + getArguments().stream() + .map(expr -> expr.accept(dynamicContext, focus)) + .collect(Collectors.toUnmodifiableList()), + dynamicContext, + focus); + } + + // the value to find, which will be the key for a map or the index for an array + IExpression argument = getArguments().stream().findFirst().get(); + + IAnyAtomicItem key = ISequence.of(argument.accept(dynamicContext, focus).atomize()) .getFirstItem(false); if (key == null) { throw new StaticMetapathException(StaticMetapathException.NO_FUNCTION_MATCH, diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/logic/Negate.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/logic/Negate.java index 71c7bcb7e..eaeadf1a3 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/logic/Negate.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/logic/Negate.java @@ -12,7 +12,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; import java.util.List; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Addition.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Addition.java index 38bb20797..3830dd03c 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Addition.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Addition.java @@ -9,7 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Division.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Division.java index 25075bcd8..c08f043db 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Division.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Division.java @@ -9,7 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/IntegerDivision.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/IntegerDivision.java index 80ecc996b..ec4749a7a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/IntegerDivision.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/IntegerDivision.java @@ -10,7 +10,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Modulo.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Modulo.java index a9d92058c..d99d7b587 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Modulo.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Modulo.java @@ -10,7 +10,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Multiplication.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Multiplication.java index 24414866a..1926ed57c 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Multiplication.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Multiplication.java @@ -9,7 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Subtraction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Subtraction.java index c42c9b32e..9ac3a1a0f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Subtraction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/math/Subtraction.java @@ -9,7 +9,7 @@ import gov.nist.secauto.metaschema.core.metapath.cst.IExpression; import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor; import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ArgumentImpl.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ArgumentImpl.java index 49ee84036..4ce49e68e 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ArgumentImpl.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ArgumentImpl.java @@ -6,6 +6,7 @@ package gov.nist.secauto.metaschema.core.metapath.function; import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import java.util.Objects; @@ -13,17 +14,17 @@ class ArgumentImpl implements IArgument { @NonNull - private final String name; + private final IEnhancedQName name; @NonNull private final ISequenceType sequenceType; - protected ArgumentImpl(@NonNull String name, @NonNull ISequenceType sequenceType) { + protected ArgumentImpl(@NonNull IEnhancedQName name, @NonNull ISequenceType sequenceType) { this.name = name; this.sequenceType = sequenceType; } @Override - public String getName() { + public IEnhancedQName getName() { return name; } @@ -38,7 +39,7 @@ public String toSignature() { StringBuilder builder = new StringBuilder(); // name - builder.append(getName()) + builder.append(getName().toEQName()) .append(" as ") .append(getSequenceType().toSignature()); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/CallingContext.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/CallingContext.java new file mode 100644 index 000000000..6fbda5d67 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/CallingContext.java @@ -0,0 +1,92 @@ + +package gov.nist.secauto.metaschema.core.metapath.function; + +import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; + +import java.util.List; +import java.util.Objects; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +public final class CallingContext { + @NonNull + private final IFunction function; + @Nullable + private final IItem contextItem; + @NonNull + private final List> arguments; + + /** + * Set up the execution context for this function. + * + * @param function + * the function + * @param arguments + * the function arguments + * @param contextItem + * the current node context + */ + public CallingContext( + @NonNull IFunction function, + @NonNull List> arguments, + @Nullable IItem contextItem) { + this.function = function; + this.contextItem = contextItem; + this.arguments = arguments; + } + + /** + * Get the function instance associated with the calling context. + * + * @return the function instance + */ + @NonNull + public IFunction getFunction() { + return function; + } + + /** + * Get the node item focus associated with the calling context. + * + * @return the function instance + */ + @Nullable + public IItem getContextItem() { + return contextItem; + } + + /** + * Get the arguments associated with the calling context. + * + * @return the arguments + */ + @NonNull + public List> getArguments() { + return arguments; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getFunction().hashCode(); + return prime * result + Objects.hash(contextItem, arguments); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; // NOPMD - readability + } + if (obj == null || getClass() != obj.getClass()) { + return false; // NOPMD - readability + } + CallingContext other = (CallingContext) obj; + if (!getFunction().equals(other.getFunction())) { + return false; // NOPMD - readability + } + return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem); + } +} diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java index f6135d79b..491c6e42d 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/ComparisonFunctions.java @@ -6,6 +6,7 @@ package gov.nist.secauto.metaschema.core.metapath.function; import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.function.library.FnNot; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java index 33cb600ba..aa21375dc 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/DefaultFunction.java @@ -6,27 +6,16 @@ package gov.nist.secauto.metaschema.core.metapath.function; import gov.nist.secauto.metaschema.core.metapath.DynamicContext; -import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; import gov.nist.secauto.metaschema.core.metapath.ISequence; import gov.nist.secauto.metaschema.core.metapath.MetapathException; -import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.function.impl.AbstractFunction; import gov.nist.secauto.metaschema.core.metapath.item.IItem; -import gov.nist.secauto.metaschema.core.metapath.item.IItemVisitor; -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.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 java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.Iterator; import java.util.List; -import java.util.Objects; import java.util.Set; -import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -85,282 +74,27 @@ public ISequenceType getResult() { } /** - * Converts arguments in an attempt to align with the function's signature. + * Execute the provided function using the provided arguments, dynamic context, + * and focus. * * @param function - * the function - * @param parameters - * the argument parameters + * the signature of the function + * @param arguments + * the function arguments * @param dynamicContext * the dynamic evaluation context - * @return the converted argument list - */ - @NonNull - public static List> convertArguments( - @NonNull IFunction function, - @NonNull List> parameters, - @NonNull DynamicContext dynamicContext) { - @NonNull - List> retval = new ArrayList<>(parameters.size()); - - Iterator argumentIterator = function.getArguments().iterator(); - IArgument argument = null; - for (ISequence parameter : parameters) { - if (argumentIterator.hasNext()) { - argument = argumentIterator.next(); - } else if (!function.isArityUnbounded()) { - throw new InvalidTypeMetapathException( - null, - String.format("argument signature doesn't match '%s'", function.toSignature())); - } - - assert argument != null; - assert parameter != null; - - retval.add(convertArgument(argument, parameter, dynamicContext)); - } - return retval; - } - - @SuppressWarnings("unused") - @NonNull - private static ISequence convertArgument( - @NonNull IArgument argument, - @NonNull ISequence parameter, - @NonNull DynamicContext dynamicContext) { - // apply occurrence - ISequence retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter); - - // apply function conversion and type promotion to the parameter - if (!retval.isEmpty()) { - IItemType type = argument.getSequenceType().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)); - } - } - } - return retval; - } - - /** - * Based on XPath 3.1 - * function - * conversion rules. - * - * @param argument - * the function argument signature details - * @param sequence - * the sequence to convert - * @param requiredSequenceType - * the expected item type for the sequence - * @return the converted sequence + * @param focus + * the current focus + * @return a sequence containing the result of the execution + * @throws MetapathException + * if an error occurred while executing the function */ - @NonNull - protected static ISequence convertSequence( - @NonNull IArgument argument, - @NonNull ISequence sequence, - @NonNull IItemType requiredSequenceType) { - Class requiredSequenceTypeClass = requiredSequenceType.getItemClass(); - - Stream stream = sequence.safeStream(); - - if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) { - Stream atomicStream = stream.flatMap(IItem::atomize); - - // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD - // // TODO: apply cast to atomic type - // } - - if (IStringItem.class.equals(requiredSequenceTypeClass)) { - // promote URIs to strings if a string is required - atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item); - } - - stream = atomicStream; - } - - stream = stream.peek(item -> { - if (!requiredSequenceTypeClass.isInstance(item)) { - throw new InvalidTypeMetapathException( - item, - String.format("The type '%s' is not a subtype of '%s'", - item.getClass().getName(), - requiredSequenceTypeClass.getName())); - } - }); - assert stream != null; - - return ISequence.of(stream); - } - - private IItem getContextItem(@NonNull ISequence focus) { - IItem contextItem = null; - if (isFocusDepenent()) { - contextItem = focus.getFirstItem(true); - if (contextItem == null) { - throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, "The context is empty"); - } - } - return contextItem; - } - @Override - public ISequence execute( - @NonNull List> arguments, + @NonNull + protected ISequence executeInternal( + @NonNull List> arguments, @NonNull DynamicContext dynamicContext, - @NonNull ISequence focus) { - - try { - IItem contextItem = getContextItem(focus); - - List> convertedArguments = convertArguments(this, arguments, dynamicContext); - - CallingContext callingContext = null; - ISequence result = null; - if (isDeterministic()) { - // check cache - callingContext = new CallingContext(convertedArguments, contextItem); - // TODO: implement something like computeIfAbsent - // attempt to get the result from the cache - result = dynamicContext.getCachedResult(callingContext); - } - - if (result == null) { - result = handler.execute(this, convertedArguments, dynamicContext, contextItem); - - if (callingContext != null) { - // add result to cache - dynamicContext.cacheResult(callingContext, result); - } - } - - // logger.info(String.format("Executed function '%s' with arguments '%s' - // producing result '%s'", - // toSignature(), convertedArguments.toString(), result.asList().toString())); - return result; - } catch (MetapathException ex) { - throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex); - } - } - - @Override - public int hashCode() { - return Objects.hash(getQName(), getArguments(), handler, properties, result); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; // NOPMD - readability - } - if (obj == null || getClass() != obj.getClass()) { - return false; // NOPMD - readability - } - DefaultFunction other = (DefaultFunction) obj; - return Objects.equals(getQName(), other.getQName()) - && Objects.equals(getArguments(), other.getArguments()) - && Objects.equals(handler, other.handler) - && Objects.equals(properties, other.properties) - && Objects.equals(result, other.result); - } - - @Override - public String toString() { - return toSignature(); - } - - public final class CallingContext { - @Nullable - private final IItem contextItem; - @NonNull - private final List> arguments; - - /** - * Set up the execution context for this function. - * - * @param arguments - * the function arguments - * @param contextItem - * the current node context - */ - private CallingContext(@NonNull List> arguments, @Nullable IItem contextItem) { - this.contextItem = contextItem; - this.arguments = arguments; - } - - /** - * Get the function instance associated with the calling context. - * - * @return the function instance - */ - @NonNull - public DefaultFunction getFunction() { - return DefaultFunction.this; - } - - /** - * Get the node item focus associated with the calling context. - * - * @return the function instance - */ - @Nullable - public IItem getContextItem() { - return contextItem; - } - - /** - * Get the arguments associated with the calling context. - * - * @return the arguments - */ - @NonNull - public List> getArguments() { - return arguments; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + getFunction().hashCode(); - return prime * result + Objects.hash(contextItem, arguments); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; // NOPMD - readability - } - if (obj == null || getClass() != obj.getClass()) { - return false; // NOPMD - readability - } - CallingContext other = (CallingContext) obj; - if (!getFunction().equals(other.getFunction())) { - return false; // NOPMD - readability - } - return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem); - } - } - - @Override - public Object getValue() { - // never a value - return null; - } - - @Override - public void accept(IItemVisitor visitor) { - visitor.visit(this); + @Nullable IItem focus) { + return handler.execute(this, arguments, dynamicContext, focus); } } diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java index af1ef9401..3adef3a9a 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/IArgument.java @@ -11,24 +11,31 @@ 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.Occurrence; +import gov.nist.secauto.metaschema.core.qname.EQNameFactory; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; import gov.nist.secauto.metaschema.core.util.ObjectUtils; import java.util.Objects; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; /** * Represents a single function argument signature. */ public interface IArgument { + @NonNull + static IArgument of(@NonNull IEnhancedQName name, @NonNull ISequenceType sequenceType) { + return new ArgumentImpl(name, sequenceType); + } + /** * Get the argument's name. * * @return the argument's name */ @NonNull - String getName(); + IEnhancedQName getName(); /** * Get information about the type of sequence supported by the argument. @@ -49,6 +56,9 @@ public interface IArgument { /** * Get a new argument builder. * + * @param staticContext + * used to resolve variable names + * * @return the new argument builder */ @NonNull @@ -56,11 +66,19 @@ static Builder builder() { return new Builder(); } + @Nullable + private static String resolveArgumentName(@NonNull String prefix) { + if (!"".equals(prefix)) { + throw new UnsupportedOperationException("Lexical qualified names are not allowed."); + } + return ""; + } + /** * Used to create an argument's signature using a builder pattern. */ final class Builder { - private String name; + private IEnhancedQName name; @NonNull private IItemType type; private Occurrence occurrence; @@ -82,7 +100,7 @@ public Builder name(@NonNull String name) { if (Objects.requireNonNull(name, "name").isBlank()) { throw new IllegalArgumentException("the name must be non-blank"); } - this.name = name.trim(); + this.name = EQNameFactory.instance().parseName(name, IArgument::resolveArgumentName); return this; } 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 434605dcf..e623ec41f 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 @@ -3,15 +3,36 @@ * SPDX-License-Identifier: CC0-1.0 */ -package gov.nist.secauto.metaschema.core.metapath.function; +package gov.nist.secauto.metaschema.core.metapath.function.impl; +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException; +import gov.nist.secauto.metaschema.core.metapath.ISequence; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.function.CallingContext; +import gov.nist.secauto.metaschema.core.metapath.function.DefaultFunction; +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.IItemVisitor; +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.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.type.IItemType; +import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException; import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; -abstract class AbstractFunction implements IFunction { +public abstract class AbstractFunction implements IFunction { @NonNull private final IEnhancedQName qname; @NonNull @@ -45,4 +66,227 @@ public int arity() { public List getArguments() { return arguments; } + + @Override + public Object getValue() { + // never a value + return null; + } + + @Override + public void accept(IItemVisitor visitor) { + visitor.visit(this); + } + + /** + * Converts arguments in an attempt to align with the function's signature. + * + * @param function + * the function + * @param parameters + * the argument parameters + * @param dynamicContext + * the dynamic evaluation context + * @return the converted argument list + */ + @NonNull + public static List> convertArguments( + @NonNull IFunction function, + @NonNull List> parameters, + @NonNull DynamicContext dynamicContext) { + @NonNull + List> retval = new ArrayList<>(parameters.size()); + + Iterator argumentIterator = function.getArguments().iterator(); + IArgument argument = null; + for (ISequence parameter : parameters) { + if (argumentIterator.hasNext()) { + argument = argumentIterator.next(); + } else if (!function.isArityUnbounded()) { + throw new InvalidTypeMetapathException( + null, + String.format("argument signature doesn't match '%s'", function.toSignature())); + } + + assert argument != null; + assert parameter != null; + + retval.add(convertArgument(argument, parameter, dynamicContext)); + } + return retval; + } + + @SuppressWarnings("unused") + @NonNull + private static ISequence convertArgument( + @NonNull IArgument argument, + @NonNull ISequence parameter, + @NonNull DynamicContext dynamicContext) { + // apply occurrence + ISequence retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter); + + // apply function conversion and type promotion to the parameter + if (!retval.isEmpty()) { + IItemType type = argument.getSequenceType().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)); + } + } + } + return retval; + } + + /** + * Based on XPath 3.1 + * function + * conversion rules. + * + * @param argument + * the function argument signature details + * @param sequence + * the sequence to convert + * @param requiredSequenceType + * the expected item type for the sequence + * @return the converted sequence + */ + @NonNull + protected static ISequence convertSequence( + @NonNull IArgument argument, + @NonNull ISequence sequence, + @NonNull IItemType requiredSequenceType) { + Class requiredSequenceTypeClass = requiredSequenceType.getItemClass(); + + Stream stream = sequence.safeStream(); + + if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) { + Stream atomicStream = stream.flatMap(IItem::atomize); + + // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD + // // TODO: apply cast to atomic type + // } + + if (IStringItem.class.equals(requiredSequenceTypeClass)) { + // promote URIs to strings if a string is required + atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item); + } + + stream = atomicStream; + } + + stream = stream.peek(item -> { + if (!requiredSequenceTypeClass.isInstance(item)) { + throw new InvalidTypeMetapathException( + item, + String.format("The type '%s' is not a subtype of '%s'", + item.getClass().getName(), + requiredSequenceTypeClass.getName())); + } + }); + assert stream != null; + + return ISequence.of(stream); + } + + private IItem getContextItem(@NonNull ISequence focus) { + IItem contextItem = focus.getFirstItem(true); + if (isFocusDepenent() && contextItem == null) { + throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, "The context is empty"); + } + return contextItem; + } + + @Override + public ISequence execute( + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + @NonNull ISequence focus) { + + try { + IItem contextItem = getContextItem(focus); + + List> convertedArguments = convertArguments(this, arguments, dynamicContext); + + CallingContext callingContext = null; + ISequence result = null; + if (isDeterministic()) { + // check cache + callingContext = new CallingContext(this, convertedArguments, contextItem); + // TODO: implement something like computeIfAbsent + // attempt to get the result from the cache + result = dynamicContext.getCachedResult(callingContext); + } + + if (result == null) { + result = executeInternal(convertedArguments, dynamicContext, contextItem); + + if (callingContext != null) { + // add result to cache + dynamicContext.cacheResult(callingContext, result); + } + } + + // logger.info(String.format("Executed function '%s' with arguments '%s' + // producing result '%s'", + // toSignature(), convertedArguments.toString(), result.asList().toString())); + return result; + } catch (MetapathException ex) { + throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex); + } + } + + /** + * Execute the provided function using the provided arguments, dynamic context, + * and focus. + * + * @param arguments + * the function arguments + * @param dynamicContext + * the dynamic evaluation context + * @param focus + * the current focus + * @return a sequence containing the result of the execution + * @throws MetapathException + * if an error occurred while executing the function + */ + @NonNull + protected abstract ISequence executeInternal( + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + @Nullable IItem focus); + + @Override + public int hashCode() { + return Objects.hash(getQName(), getArguments(), getProperties(), getResult()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; // NOPMD - readability + } + if (obj == null || getClass() != obj.getClass()) { + return false; // NOPMD - readability + } + DefaultFunction other = (DefaultFunction) obj; + return Objects.equals(getQName(), other.getQName()) + && Objects.equals(getArguments(), other.getArguments()) + && Objects.equals(getProperties(), other.getProperties()) + && Objects.equals(getResult(), other.getResult()); + } + + @Override + public String toString() { + return toSignature(); + } } 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 3f9145e98..b12dc2e2c 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 @@ -3,8 +3,11 @@ * SPDX-License-Identifier: CC0-1.0 */ -package gov.nist.secauto.metaschema.core.metapath.function; // NOPMD - intentional +package gov.nist.secauto.metaschema.core.metapath.function.impl; // NOPMD - intentional +import gov.nist.secauto.metaschema.core.metapath.function.ArithmeticFunctionException; +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.IBase64BinaryItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java index 299182971..ecf59249f 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnAvg.java @@ -12,7 +12,7 @@ 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.function.InvalidArgumentFunctionException; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; 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.IDayTimeDurationItem; diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnSum.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnSum.java index cd7c98c5d..131b0899b 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnSum.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnSum.java @@ -12,7 +12,7 @@ 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.function.InvalidArgumentFunctionException; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; 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.IDayTimeDurationItem; diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/OperationFunctionsTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/OperationFunctionsTest.java index 2bfffb40a..69628dd63 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/OperationFunctionsTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/OperationFunctionsTest.java @@ -9,7 +9,7 @@ import static gov.nist.secauto.metaschema.core.metapath.TestUtils.integer; import static org.junit.jupiter.api.Assertions.assertEquals; -import gov.nist.secauto.metaschema.core.metapath.function.OperationFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem; diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCallTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCallTest.java new file mode 100644 index 000000000..f13d2150a --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/cst/AnonymousFunctionCallTest.java @@ -0,0 +1,37 @@ + +package gov.nist.secauto.metaschema.core.metapath.cst; + +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.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.api.Test; + +class AnonymousFunctionCallTest { + private static final String NS = "http://example.com/ns"; + + @Test + void test() { + StaticContext staticContext = StaticContext.builder() + .namespace("ex", NS) + .build(); + DynamicContext dynamicContext = new DynamicContext(staticContext); + dynamicContext.bindVariableValue(IEnhancedQName.of(NS, "var1"), ISequence.of(string("fn:empty"))); + + String metapath = "let $function := function($str) as meta:string { fn:concat('extra ',$str) } " + + "return $function('cool')"; + + assertEquals( + "extra cool", + MetapathExpression.compile(metapath, staticContext).evaluateAs( + null, + MetapathExpression.ResultType.STRING, + dynamicContext)); + } + +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctionsTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctionsTest.java index a9eb83e80..fca82d17b 100644 --- a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctionsTest.java +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/OperationFunctionsTest.java @@ -9,6 +9,7 @@ import static gov.nist.secauto.metaschema.core.metapath.TestUtils.integer; import static org.junit.jupiter.api.Assertions.assertEquals; +import gov.nist.secauto.metaschema.core.metapath.function.impl.OperationFunctions; import gov.nist.secauto.metaschema.core.metapath.function.library.FunctionTestBase; import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;