Skip to content

Commit

Permalink
Added support for the Metapath zero-or-one, one-or-more, and exactly-…
Browse files Browse the repository at this point in the history
…one functions. Resolves #280 (#281)
  • Loading branch information
david-waltermire authored Dec 10, 2024
1 parent 0d676b4 commit deb73be
Show file tree
Hide file tree
Showing 9 changed files with 491 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,41 @@ public class InvalidArgumentFunctionException
* Raised when either argument to fn:resolve-uri is not a valid URI/IRI.
*/
public static final int INVALID_ARGUMENT_TO_RESOLVE_URI = 2;
/**
* <a href=
* "https://www.w3.org/TR/xpath-functions-31/#ERRFORG0003">err:FORG0003</a>:
* Raised by <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-zero-or-one">fn:zero-or-one</a>
* if the supplied value contains more than one item.
*
*/
public static final int INVALID_ARGUMENT_ZERO_OR_ONE = 3;
/**
* <a href=
* "https://www.w3.org/TR/xpath-functions-31/#ERRFORG0004">err:FORG0005</a>:
* Raised by <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-one-or-more">fn:one-or-more</a>
* if the supplied value is an empty sequence.
*/
public static final int INVALID_ARGUMENT_ONE_OR_MORE = 4;
/**
* <a href=
* "https://www.w3.org/TR/xpath-functions-31/#ERRFORG0005">err:FORG0005</a>:
* Raised by <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-exactly-one">fn:exactly-one</a>
* if the supplied value is not a singleton sequence.
*
*/
public static final int INVALID_ARGUMENT_EXACTLY_ONE = 5;
/**
* <a href=
* "https://www.w3.org/TR/xpath-functions-31/#ERRFORG0006">err:FORG0006</a>:
* Raised by functions such as fn:max, fn:min, fn:avg, fn:sum if the supplied
* sequence contains values inappropriate to this function.
* Raised by functions such as
* <a href="https://www.w3.org/TR/xpath-functions-31/#func-max">fn:max</a>,
* <a href="https://www.w3.org/TR/xpath-functions-31/#func-min">fn:min</a>,
* <a href="https://www.w3.org/TR/xpath-functions-31/#func-avg">fn:avg</a>,
* <a href="https://www.w3.org/TR/xpath-functions-31/#func-sum">fn:sum</a> if
* the supplied sequence contains values inappropriate to this function.
*/
public static final int INVALID_ARGUMENT_TYPE = 6;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ public ISequence<?> execute(
// toSignature(), convertedArguments.toString(), result.asList().toString()));
return result;
} catch (MetapathException ex) {
throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex);
// FIXME: avoid throwing a new exception for a function-related exception. Fix
// this after refactoring the exception hierarchy.
throw new MetapathException(String.format("Unable to execute function '%s'. %s",
toSignature(),
ex.getLocalizedMessage()),
ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// https://www.w3.org/TR/xpath-functions-31/#func-encode-for-uri
// https://www.w3.org/TR/xpath-functions-31/#func-ends-with
registerFunction(FnEndsWith.SIGNATURE);
// P2: https://www.w3.org/TR/xpath-functions-31/#func-exactly-one
// https://www.w3.org/TR/xpath-functions-31/#func-exactly-one
registerFunction(FnExactlyOne.SIGNATURE);
// https://www.w3.org/TR/xpath-functions-31/#func-exists
registerFunction(FnExists.SIGNATURE);
// https://www.w3.org/TR/xpath-functions-31/#func-false
Expand Down Expand Up @@ -129,7 +130,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// https://www.w3.org/TR/xpath-functions-31/#func-not
registerFunction(FnNot.SIGNATURE);
// https://www.w3.org/TR/xpath-functions-31/#func-number
// P2: https://www.w3.org/TR/xpath-functions-31/#func-one-or-more
// https://www.w3.org/TR/xpath-functions-31/#func-one-or-more
registerFunction(FnOneOrMore.SIGNATURE);
// https://www.w3.org/TR/xpath-functions-31/#func-outermost
// https://www.w3.org/TR/xpath-functions-31/#func-parse-ietf-date
// https://www.w3.org/TR/xpath-functions-31/#func-path
Expand Down Expand Up @@ -194,7 +196,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// https://www.w3.org/TR/xpath-functions-31/#func-year-from-date
// https://www.w3.org/TR/xpath-functions-31/#func-year-from-dateTime
// https://www.w3.org/TR/xpath-functions-31/#func-years-from-duration
// P2: https://www.w3.org/TR/xpath-functions-31/#func-zero-or-one
// https://www.w3.org/TR/xpath-functions-31/#func-zero-or-one
registerFunction(FnZeroOrOne.SIGNATURE);

// https://www.w3.org/TR/xpath-functions-31/#func-array-get
registerFunction(ArrayGet.SIGNATURE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.metaschema.core.metapath.function.library;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
import gov.nist.secauto.metaschema.core.metapath.function.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.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Implements the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-exactly-one">fn:exactly-one</a>
* function.
*/
public final class FnExactlyOne {
private static final String NAME = "exactly-one";
@NonNull
static final IFunction SIGNATURE = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("arg")
.type(IItem.type())
.zeroOrMore()
.build())
.returnType(IItem.type())
.returnOne()
.functionHandler(FnExactlyOne::execute)
.build();

private FnExactlyOne() {
// disable construction
}

@SuppressWarnings("unused")
@NonNull
private static ISequence<?> execute(@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
return fnExactlyOne(ObjectUtils.requireNonNull(arguments.get(0)));
}

/**
* Check that the provided sequence has exactly one item.
* <p>
* Based on the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-exactly-one">fn:exactly-one</a>
* function.
*
* @param sequence
* the sequence to evaluate
* @return the sequence if it has zero or one items
* @throws InvalidArgumentFunctionException
* with the code
* {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_EXACTLY_ONE}
* if the sequence contains less or more than one item
*/
@NonNull
public static ISequence<?> fnExactlyOne(@NonNull ISequence<?> sequence) {
if (sequence.size() != 1) {
throw new InvalidArgumentFunctionException(
InvalidArgumentFunctionException.INVALID_ARGUMENT_EXACTLY_ONE,
String.format("fn:exactly-one called with the sequence '%s' containing a number of items other than one.",
sequence.toSignature()));
}
return sequence;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.metaschema.core.metapath.function.library;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
import gov.nist.secauto.metaschema.core.metapath.function.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.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Implements the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-one-or-more">fn:one-or-more</a>
* function.
*/
public final class FnOneOrMore {
private static final String NAME = "one-or-more";
@NonNull
static final IFunction SIGNATURE = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("arg")
.type(IItem.type())
.zeroOrMore()
.build())
.returnType(IItem.type())
.returnOneOrMore()
.functionHandler(FnOneOrMore::execute)
.build();

private FnOneOrMore() {
// disable construction
}

@SuppressWarnings("unused")
@NonNull
private static ISequence<?> execute(@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
return fnOneOrMore(ObjectUtils.requireNonNull(arguments.get(0)));
}

/**
* Check that the provided sequence has one or more items.
* <p>
* Based on the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-one-or-more">fn:one-or-more</a>
* function.
*
* @param sequence
* the sequence to evaluate
* @return the sequence if it has zero or one items
* @throws InvalidArgumentFunctionException
* with the code
* {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_ONE_OR_MORE}
* if the sequence contains no items
*/
@NonNull
public static ISequence<?> fnOneOrMore(@NonNull ISequence<?> sequence) {
if (sequence.size() < 1) {
throw new InvalidArgumentFunctionException(
InvalidArgumentFunctionException.INVALID_ARGUMENT_ONE_OR_MORE,
String.format("fn:one-or-more called with the sequence '%s' containing less than one item.",
sequence.toSignature()));
}
return sequence;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.metaschema.core.metapath.function.library;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
import gov.nist.secauto.metaschema.core.metapath.function.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.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Implements the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-zero-or-one">fn:zero-or-one</a>
* function.
*/
public final class FnZeroOrOne {
private static final String NAME = "zero-or-one";
@NonNull
static final IFunction SIGNATURE = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("arg")
.type(IItem.type())
.zeroOrMore()
.build())
.returnType(IItem.type())
.returnZeroOrOne()
.functionHandler(FnZeroOrOne::execute)
.build();

private FnZeroOrOne() {
// disable construction
}

@SuppressWarnings("unused")
@NonNull
private static ISequence<?> execute(@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
return fnZeroOrOne(ObjectUtils.requireNonNull(arguments.get(0)));
}

/**
* Check that the provided sequence has zero or one items.
* <p>
* Based on the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-zero-or-one">fn:zero-or-one</a>
* function.
*
* @param sequence
* the sequence to evaluate
* @return the sequence if it has zero or one items
* @throws InvalidArgumentFunctionException
* with the code
* {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_ZERO_OR_ONE}
* if the sequence contains more than one item
*/
@NonNull
public static ISequence<?> fnZeroOrOne(@NonNull ISequence<?> sequence) {
if (sequence.size() > 1) {
throw new InvalidArgumentFunctionException(
InvalidArgumentFunctionException.INVALID_ARGUMENT_ZERO_OR_ONE,
String.format("fn:zero-or-one called with the sequence '%s' containing more than one item.",
sequence.toSignature()));
}
return sequence;
}
}
Loading

0 comments on commit deb73be

Please sign in to comment.