Skip to content

Commit

Permalink
Added support for lazy Metapath compilation. Cleaned up well-known na…
Browse files Browse the repository at this point in the history
…mespace and prefix support, moving this functionality to a new WellKnown class.
  • Loading branch information
david-waltermire committed Dec 2, 2024
1 parent a9987e6 commit c632594
Show file tree
Hide file tree
Showing 17 changed files with 336 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean;
import gov.nist.secauto.metaschema.core.metapath.impl.LazyCompilationMetapathExpression;
import gov.nist.secauto.metaschema.core.metapath.impl.MetapathExpression;
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
Expand Down Expand Up @@ -145,6 +146,23 @@ static IMetapathExpression compile(@NonNull String path, @NonNull StaticContext
return MetapathExpression.compile(path, staticContext);
}

/**
* Gets a new Metapath expression that is compiled on use.
* <p>
* Lazy compilation may cause additional {@link MetapathException} errors at
* evaluation time, since compilation errors are not raised until evaluation.
*
* @param path
* the metapath expression
* @param staticContext
* the static evaluation context
* @return the expression object
*/
@NonNull
static IMetapathExpression lazyCompile(@NonNull String path, @NonNull StaticContext staticContext) {
return new LazyCompilationMetapathExpression(path, staticContext);
}

/**
* Get the original Metapath expression as a string.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,36 @@
import gov.nist.secauto.metaschema.core.datatype.DataTypeService;
import gov.nist.secauto.metaschema.core.metapath.function.FunctionService;
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
import gov.nist.secauto.metaschema.core.metapath.function.IFunctionResolver;
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.type.IAtomicOrUnionType;
import gov.nist.secauto.metaschema.core.metapath.type.IItemType;
import gov.nist.secauto.metaschema.core.qname.EQNameFactory;
import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
import gov.nist.secauto.metaschema.core.qname.NamespaceCache;
import gov.nist.secauto.metaschema.core.qname.WellKnown;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.CustomCollectors;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.xml.XMLConstants;

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

// add support for default namespace
/**
* The implementation of a Metapath
* <a href="https://www.w3.org/TR/xpath-31/#static_context">static context</a>.
*/
// FIXME: refactor well-known into a new class
public final class StaticContext {
@NonNull
private static final Map<String, String> WELL_KNOWN_NAMESPACES;
@NonNull
private static final Map<String, String> WELL_KNOWN_URI_TO_PREFIX;

static {
Map<String, String> knownNamespaces = new ConcurrentHashMap<>();
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH,
MetapathConstants.NS_METAPATH);
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH_FUNCTIONS,
MetapathConstants.NS_METAPATH_FUNCTIONS);
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH_FUNCTIONS_MATH,
MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH_FUNCTIONS_ARRAY,
MetapathConstants.NS_METAPATH_FUNCTIONS_ARRAY);
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH_FUNCTIONS_MAP,
MetapathConstants.NS_METAPATH_FUNCTIONS_MAP);
WELL_KNOWN_NAMESPACES = CollectionUtil.unmodifiableMap(knownNamespaces);

WELL_KNOWN_NAMESPACES.forEach(
(prefix, namespace) -> NamespaceCache.instance().indexOf(ObjectUtils.notNull(namespace)));

WELL_KNOWN_URI_TO_PREFIX = ObjectUtils.notNull(WELL_KNOWN_NAMESPACES.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
(Function<? super Entry<String, String>, ? extends String>) Entry::getValue,
Map.Entry::getKey,
(v1, v2) -> v1)));
}

@Nullable
private final URI baseUri;
Expand All @@ -86,44 +50,8 @@ public final class StaticContext {
@Nullable
private final String defaultFunctionNamespace;
private final boolean useWildcardWhenNamespaceNotDefaulted;

/**
* Get the mapping of prefix to namespace URI for all well-known namespaces
* provided by default to the static context.
* <p>
* These namespaces can be overridden using the
* {@link Builder#namespace(String, URI)} method.
*
* @return the mapping of prefix to namespace URI for all well-known namespaces
*/
@SuppressFBWarnings("MS_EXPOSE_REP")
public static Map<String, String> getWellKnownNamespacesMap() {
return WELL_KNOWN_NAMESPACES;
}

/**
* Get the mapping of namespace URIs to prefixes for all well-known namespaces
* provided by default to the static context.
*
* @return the mapping of namespace URI to prefix for all well-known namespaces
*/
@SuppressFBWarnings("MS_EXPOSE_REP")
public static Map<String, String> getWellKnownURIToPrefixMap() {
return WELL_KNOWN_URI_TO_PREFIX;
}

/**
* Get the namespace prefix associated with the provided URI, if the URI is
* well-known.
*
* @param uri
* the URI to get the prefix for
* @return the prefix or {@code null} if the provided URI is not well-known
*/
@Nullable
public static String getWellKnownPrefixForUri(@NonNull String uri) {
return WELL_KNOWN_URI_TO_PREFIX.get(uri);
}
@NonNull
private final IFunctionResolver functionResolver;

/**
* Create a new static context instance using default values.
Expand All @@ -147,6 +75,7 @@ private StaticContext(Builder builder) {
this.defaultModelNamespace = builder.defaultModelNamespace;
this.defaultFunctionNamespace = builder.defaultFunctionNamespace;
this.useWildcardWhenNamespaceNotDefaulted = builder.useWildcardWhenNamespaceNotDefaulted;
this.functionResolver = builder.functionResolver;
}

/**
Expand Down Expand Up @@ -184,7 +113,7 @@ private String lookupNamespaceURIForPrefix(@NonNull String prefix) {
String retval = knownPrefixToNamespace.get(prefix);
if (retval == null) {
// fall back to well-known namespaces
retval = WELL_KNOWN_NAMESPACES.get(prefix);
retval = WellKnown.getWellKnownUriForPrefix(prefix);
}
return retval;
}
Expand All @@ -194,7 +123,7 @@ private String lookupPrefixForNamespaceURI(@NonNull String namespace) {
String retval = knownNamespacesToPrefix.get(namespace);
if (retval == null) {
// fall back to well-known namespaces
retval = WELL_KNOWN_URI_TO_PREFIX.get(namespace);
retval = WellKnown.getWellKnownPrefixForUri(namespace);
}
return retval;
}
Expand Down Expand Up @@ -466,8 +395,8 @@ public IFunction lookupFunction(@NonNull String name, int arity) {
* matching function was not found
*/
@NonNull
public static IFunction lookupFunction(@NonNull IEnhancedQName qname, int arity) {
return FunctionService.getInstance().getFunction(
public IFunction lookupFunction(@NonNull IEnhancedQName qname, int arity) {
return functionResolver.getFunction(
Objects.requireNonNull(qname, "name"),
arity);
}
Expand Down Expand Up @@ -628,6 +557,8 @@ public static final class Builder {
private String defaultModelNamespace;
@Nullable
private String defaultFunctionNamespace = MetapathConstants.NS_METAPATH_FUNCTIONS;
@NonNull
private IFunctionResolver functionResolver = FunctionService.instance();

private Builder() {
// avoid direct construction
Expand Down Expand Up @@ -657,15 +588,15 @@ public Builder baseUri(@NonNull URI uri) {
* {@link StaticContext#lookupNamespaceForPrefix(String)} method.
* <p>
* Well-known namespace bindings are used by default, which can be retrieved
* using the {@link StaticContext#getWellKnownNamespacesMap()} method.
* using the {@link WellKnown#getWellKnownUriForPrefix(String)} method.
*
* @param prefix
* the prefix to associate with the namespace, which may be
* @param uri
* the namespace URI
* @return this builder
* @see StaticContext#lookupNamespaceForPrefix(String)
* @see StaticContext#getWellKnownNamespacesMap()
* @see WellKnown#getWellKnownUriForPrefix(String)
*/
@NonNull
public Builder namespace(@NonNull String prefix, @NonNull URI uri) {
Expand All @@ -683,7 +614,7 @@ public Builder namespace(@NonNull String prefix, @NonNull URI uri) {
* @throws IllegalArgumentException
* if the provided prefix or URI is invalid
* @see StaticContext#lookupNamespaceForPrefix(String)
* @see StaticContext#getWellKnownNamespacesMap()
* @see WellKnown#getWellKnownUriForPrefix(String)
*/
@NonNull
public Builder namespace(@NonNull String prefix, @NonNull String uri) {
Expand Down Expand Up @@ -776,38 +707,36 @@ public Builder defaultFunctionNamespace(@NonNull String uri) {
* {@code true} if on or {@code false} otherwise
* @return this builder
*/
@NonNull
public Builder useWildcardWhenNamespaceNotDefaulted(boolean value) {
this.useWildcardWhenNamespaceNotDefaulted = value;
return this;
}

/**
* Construct a new static context using the information provided to the builder.
* Set the function resolver used to lookup function implementations.
* <p>
* By default, the {@link FunctionService} is used to load function
* implementations using the service provider interface.
*
* @return the new static context
* @param resolver
* the resolver to use instead of the default resolver
* @return this builder
*/
@NonNull
public StaticContext build() {
return new StaticContext(this);
public Builder functionResolver(@NonNull IFunctionResolver resolver) {
this.functionResolver = resolver;
return this;
}
}

/**
* Provides a callback for resolving namespace prefixes.
*/
@FunctionalInterface
public interface EQNameResolver {
/**
* Get the URI string for the provided namespace prefix.
* Construct a new static context using the information provided to the builder.
*
* @param name
* the name to resolve
* @return the URI string or {@code null} if the prefix is unbound
* @throws StaticMetapathError
* with the code {@link StaticMetapathError#PREFIX_NOT_EXPANDABLE} if
* a non-empty prefix is provided
* @return the new static context
*/
@NonNull
IEnhancedQName resolve(@NonNull String name);
public StaticContext build() {
return new StaticContext(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ protected IExpression handleFunctioncall(Metapath10.FunctioncallContext ctx) {
.collect(Collectors.toUnmodifiableList()));

return new StaticFunctionCall(
() -> getContext().lookupFunction(
getContext().lookupFunction(
ObjectUtils.notNull(ctx.eqname().getText()),
arguments.size()),
arguments);
Expand Down Expand Up @@ -1230,7 +1230,7 @@ protected IExpression handleArrowexpr(Metapath10.ArrowexprContext context) {
if (arrowCtx.eqname() != null) {
// named function
return new StaticFunctionCall(
() -> getContext().lookupFunction(ObjectUtils.notNull(arrowCtx.eqname().getText()), arguments.size()),
getContext().lookupFunction(ObjectUtils.notNull(arrowCtx.eqname().getText()), arguments.size()),
arguments);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import edu.umd.cs.findbugs.annotations.NonNull;
import nl.talsmasoftware.lazy4j.Lazy;

/**
* Executes a function call based on the provided function and multiple argument
Expand All @@ -31,26 +29,22 @@
* Static functions are resolved during the parsing phase and must exist in the
* function registry.
*/
// FIXME: Change compilation to error when a non-existant function is called.
// Manage this error where the compilation is requested
public class StaticFunctionCall implements IExpression {
@NonNull
private final Lazy<IFunction> functionSupplier;
private final IFunction function;
@NonNull
private final List<IExpression> arguments;

/**
* Construct a new function call expression.
*
* @param functionSupplier
* the function supplier, which is used to lazy fetch the function
* allowing the containing Metapaths to parse even if a function does
* not exist during the parsing phase.
* @param function
* the function implementation
* @param arguments
* the expressions used to provide arguments to the function call
*/
public StaticFunctionCall(@NonNull Supplier<IFunction> functionSupplier, @NonNull List<IExpression> arguments) {
this.functionSupplier = ObjectUtils.notNull(Lazy.lazy(functionSupplier));
public StaticFunctionCall(@NonNull IFunction function, @NonNull List<IExpression> arguments) {
this.function = function;
this.arguments = arguments;
}

Expand All @@ -64,13 +58,6 @@ public StaticFunctionCall(@NonNull Supplier<IFunction> functionSupplier, @NonNul
*/
@NonNull
public IFunction getFunction() {
IFunction function = functionSupplier.get();
if (function == null) {
throw new StaticMetapathError(
StaticMetapathError.NO_FUNCTION_MATCH,
String.format(
"No matching function found for the given name and arguments"));
}
return function;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import nl.talsmasoftware.lazy4j.Lazy;

public final class FunctionService {
public final class FunctionService implements IFunctionResolver {
private static final Lazy<FunctionService> INSTANCE = Lazy.lazy(FunctionService::new);
@NonNull
private final ServiceLoader<IFunctionLibrary> loader;
Expand All @@ -28,7 +28,8 @@ public final class FunctionService {
*
* @return the service instance
*/
public static FunctionService getInstance() {
@NonNull
public static FunctionService instance() {
return INSTANCE.get();
}

Expand Down Expand Up @@ -75,20 +76,7 @@ public Stream<IFunction> stream() {
return getLibrary().stream();
}

/**
* Retrieve the function with the provided name that supports the signature of
* the provided methods, if such a function exists.
*
* @param name
* the name of a group of functions
* @param arity
* the count of arguments for use in determining an argument signature
* match
* @return the matching function or {@code null} if no match exists
* @throws StaticMetapathError
* with the code {@link StaticMetapathError#NO_FUNCTION_MATCH} if a
* matching function was not found
*/
@Override
@NonNull
public IFunction getFunction(@NonNull IEnhancedQName name, int arity) {
IFunction retval = getLibrary().getFunction(name, arity);
Expand Down
Loading

0 comments on commit c632594

Please sign in to comment.