StaticContext.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.core.metapath;
import gov.nist.secauto.metaschema.core.metapath.EQNameUtils.IEQNamePrefixResolver;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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>.
*/
public final class StaticContext {
@NonNull
private static final Map<String, URI> WELL_KNOWN_NAMESPACES;
@NonNull
private static final Map<String, String> WELL_KNOWN_URI_TO_PREFIX;
static {
Map<String, URI> knownNamespaces = new ConcurrentHashMap<>();
knownNamespaces.put(
MetapathConstants.PREFIX_METAPATH,
MetapathConstants.NS_METAPATH);
knownNamespaces.put(
MetapathConstants.PREFIX_XML_SCHEMA,
MetapathConstants.NS_XML_SCHEMA);
knownNamespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS,
MetapathConstants.NS_METAPATH_FUNCTIONS);
knownNamespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS_MATH,
MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
knownNamespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS_ARRAY,
MetapathConstants.NS_METAPATH_FUNCTIONS_ARRAY);
knownNamespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS_MAP,
MetapathConstants.NS_METAPATH_FUNCTIONS_MAP);
WELL_KNOWN_NAMESPACES = CollectionUtil.unmodifiableMap(knownNamespaces);
WELL_KNOWN_URI_TO_PREFIX = ObjectUtils.notNull(WELL_KNOWN_NAMESPACES.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
entry -> entry.getValue().toASCIIString(),
Map.Entry::getKey,
(v1, v2) -> v2)));
}
@Nullable
private final URI baseUri;
@NonNull
private final Map<String, URI> knownNamespaces;
@Nullable
private final URI defaultModelNamespace;
@Nullable
private final URI 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, URI> 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);
}
/**
* Create a new static context instance using default values.
*
* @return a new static context instance
*/
@NonNull
public static StaticContext instance() {
return builder().build();
}
private StaticContext(Builder builder) {
this.baseUri = builder.baseUri;
this.knownNamespaces = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Map.copyOf(builder.namespaces)));
this.defaultModelNamespace = builder.defaultModelNamespace;
this.defaultFunctionNamespace = builder.defaultFunctionNamespace;
this.useWildcardWhenNamespaceNotDefaulted = builder.useWildcardWhenNamespaceNotDefaulted;
}
/**
* Get the static base URI to use in resolving URIs handled by the Metapath
* processor. This URI, if provided, will be used when a document base URI is
* not available.
*
* @return the base URI or {@code null} if not defined
*/
@Nullable
public URI getBaseUri() {
return baseUri;
}
/**
* Get the namespace URI associated with the provided {@code prefix}, if any is
* bound.
* <p>
* This method uses the namespaces set by the
* {@link Builder#namespace(String, URI)} method, falling back to the well-known
* namespace bindings when a prefix match is not found.
* <p>
* The well-known namespace bindings can be retrieved using the
* {@link StaticContext#getWellKnownNamespacesMap()} method.
*
* @param prefix
* the namespace prefix
* @return the namespace URI bound to the prefix, or {@code null} if no
* namespace is bound to the prefix
* @see Builder#namespace(String, URI)
* @see #getWellKnownNamespacesMap()
*/
@Nullable
public URI lookupNamespaceURIForPrefix(@NonNull String prefix) {
URI retval = knownNamespaces.get(prefix);
if (retval == null) {
// fall back to well-known namespaces
retval = WELL_KNOWN_NAMESPACES.get(prefix);
}
return retval;
}
/**
* Get the namespace associated with the provided {@code prefix} as a string, if
* any is bound.
*
* @param prefix
* the namespace prefix
* @return the namespace string bound to the prefix, or {@code null} if no
* namespace is bound to the prefix
*/
@Nullable
public String lookupNamespaceForPrefix(@NonNull String prefix) {
URI result = lookupNamespaceURIForPrefix(prefix);
return result == null ? null : result.toASCIIString();
}
/**
* Get the default namespace for assembly, field, or flag references that have
* no namespace prefix.
*
* @return the namespace if defined or {@code null} otherwise
*/
@Nullable
public URI getDefaultModelNamespace() {
return defaultModelNamespace;
}
/**
* Get the default namespace for function references that have no namespace
* prefix.
*
* @return the namespace if defined or {@code null} otherwise
*/
@Nullable
public URI getDefaultFunctionNamespace() {
return defaultFunctionNamespace;
}
/**
* Get a prefix resolver for use with Metapath function names that will attempt
* to identify the namespace corresponding to a given prefix.
* <p>
* This will use the following lookup order, advancing to the next when a
* {@code null} value is returned:
* <ol>
* <li>Lookup the prefix using
* {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
* <li>Return the result of
* {@link StaticContext#getDefaultFunctionNamespace()}</li>
* <li>Return {@link XMLConstants#NULL_NS_URI}</li>
* </ol>
*
* @return the resolver
*/
@NonNull
public IEQNamePrefixResolver getFunctionPrefixResolver() {
return this::resolveFunctionPrefix;
}
@NonNull
private String resolveFunctionPrefix(@NonNull String prefix) {
String ns = lookupNamespaceForPrefix(prefix);
if (ns == null) {
URI uri = getDefaultFunctionNamespace();
if (uri != null) {
ns = uri.toASCIIString();
}
}
return ns == null ? XMLConstants.NULL_NS_URI : ns;
}
/**
* Get a prefix resolver for use with Metapath flag node names that will attempt
* to identify the namespace corresponding to a given prefix.
* <p>
* This will use the following lookup order, advancing to the next when a
* {@code null} value is returned:
* <ol>
* <li>Lookup the prefix using
* {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
* <li>Return {@link XMLConstants#NULL_NS_URI}</li>
* </ol>
*
* @return the resolver
*/
@NonNull
public IEQNamePrefixResolver getFlagPrefixResolver() {
return this::resolveFlagReferencePrefix;
}
@NonNull
private String resolveFlagReferencePrefix(@NonNull String prefix) {
String ns = lookupNamespaceForPrefix(prefix);
return ns == null ? XMLConstants.NULL_NS_URI : ns;
}
/**
* Get a prefix resolver for use with Metapath model node names that will
* attempt to identify the namespace corresponding to a given prefix.
* <p>
* This will use the following lookup order, advancing to the next when a
* {@code null} value is returned:
* <ol>
* <li>Lookup the prefix using
* {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
* <li>Return the result of
* {@link StaticContext#getDefaultModelNamespace()}</li>
* <li>Return {@link XMLConstants#NULL_NS_URI}</li>
* </ol>
*
* @return the resolver
*/
@NonNull
public IEQNamePrefixResolver getModelPrefixResolver() {
return this::resolveModelReferencePrefix;
}
@NonNull
private String resolveModelReferencePrefix(@NonNull String prefix) {
String ns = lookupNamespaceForPrefix(prefix);
if (ns == null) {
URI uri = getDefaultModelNamespace();
if (uri != null) {
ns = uri.toASCIIString();
}
}
return ns == null ? XMLConstants.NULL_NS_URI : ns;
}
/**
* Get a prefix resolver for use with Metapath variable names that will attempt
* to identify the namespace corresponding to a given prefix.
* <p>
* This will use the following lookup order, advancing to the next when a
* {@code null} value is returned:
* <ol>
* <li>Lookup the prefix using
* {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
* <li>Return {@link XMLConstants#NULL_NS_URI}</li>
* </ol>
*
* @return the resolver
*/
@NonNull
public IEQNamePrefixResolver getVariablePrefixResolver() {
return this::resolveVariablePrefix;
}
@NonNull
private String resolveVariablePrefix(@NonNull String prefix) {
String ns = lookupNamespaceForPrefix(prefix);
return ns == null ? XMLConstants.NULL_NS_URI : ns;
}
/**
* Get a new static context builder that is pre-populated with the setting of
* this static context.
*
* @return a new builder
*/
@NonNull
public Builder buildFrom() {
Builder builder = builder();
builder.baseUri = this.baseUri;
builder.namespaces.putAll(this.knownNamespaces);
builder.defaultModelNamespace = this.defaultModelNamespace;
builder.defaultFunctionNamespace = this.defaultFunctionNamespace;
return builder;
}
/**
* Indicates if a name match should use a wildcard for the namespace is the
* namespace does not have a value and the {@link #getDefaultModelNamespace()}
* is {@code null}.
*
* @return {@code true} if a wildcard match on the name space should be used or
* {@code false} otherwise
*/
public boolean isUseWildcardWhenNamespaceNotDefaulted() {
return useWildcardWhenNamespaceNotDefaulted && getDefaultModelNamespace() == null;
}
/**
* Create a new static context builder that allows for fine-grained adjustments
* when creating a new static context.
*
* @return a new builder
*/
@NonNull
public static Builder builder() {
return new Builder();
}
/**
* A builder used to generate the static context.
*/
public static final class Builder {
public boolean useWildcardWhenNamespaceNotDefaulted; // false
@Nullable
private URI baseUri;
@NonNull
private final Map<String, URI> namespaces = new ConcurrentHashMap<>();
@Nullable
private URI defaultModelNamespace;
@Nullable
private URI defaultFunctionNamespace = MetapathConstants.NS_METAPATH_FUNCTIONS;
private Builder() {
namespaces.put(
MetapathConstants.PREFIX_METAPATH,
MetapathConstants.NS_METAPATH);
namespaces.put(
MetapathConstants.PREFIX_XML_SCHEMA,
MetapathConstants.NS_XML_SCHEMA);
namespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS,
MetapathConstants.NS_METAPATH_FUNCTIONS);
namespaces.put(
MetapathConstants.PREFIX_XPATH_FUNCTIONS_MATH,
MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
}
/**
* Sets the static base URI to use in resolving URIs handled by the Metapath
* processor, when a document base URI is not available. There is only a single
* base URI. Subsequent calls to this method will change the base URI.
*
* @param uri
* the base URI to use
* @return this builder
*/
@NonNull
public Builder baseUri(@NonNull URI uri) {
this.baseUri = uri;
return this;
}
/**
* Adds a new prefix to namespace URI binding to the mapping of
* <a href="https://www.w3.org/TR/xpath-31/#dt-static-namespaces">statically
* known namespaces</a>.
* <p>
* A namespace set by this method can be resolved using the
* {@link StaticContext#lookupNamespaceForPrefix(String)} method.
* <p>
* Well-known namespace bindings are used by default, which can be retrieved
* using the {@link StaticContext#getWellKnownNamespacesMap()} 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#lookupNamespaceURIForPrefix(String)
* @see StaticContext#getWellKnownNamespacesMap()
*/
@NonNull
public Builder namespace(@NonNull String prefix, @NonNull URI uri) {
this.namespaces.put(prefix, uri);
return this;
}
/**
* A convenience method for {@link #namespace(String, URI)}.
*
* @param prefix
* the prefix to associate with the namespace, which may be
* @param uri
* the namespace URI
* @return this builder
* @throws IllegalArgumentException
* if the provided URI is invalid
* @see StaticContext#lookupNamespaceForPrefix(String)
* @see StaticContext#lookupNamespaceURIForPrefix(String)
* @see StaticContext#getWellKnownNamespacesMap()
*/
@NonNull
public Builder namespace(@NonNull String prefix, @NonNull String uri) {
return namespace(prefix, ObjectUtils.notNull(URI.create(uri)));
}
/**
* Defines the default namespace to use for assembly, field, or flag references
* that have no namespace prefix.
*
* @param uri
* the namespace URI
* @return this builder
* @see StaticContext#getDefaultModelNamespace()
*/
@NonNull
public Builder defaultModelNamespace(@NonNull URI uri) {
this.defaultModelNamespace = uri;
return this;
}
/**
* A convenience method for {@link #defaultModelNamespace(URI)}.
*
* @param uri
* the namespace URI
* @return this builder
* @throws IllegalArgumentException
* if the provided URI is invalid
* @see StaticContext#getDefaultModelNamespace()
*/
@NonNull
public Builder defaultModelNamespace(@NonNull String uri) {
return defaultModelNamespace(ObjectUtils.notNull(URI.create(uri)));
}
/**
* Defines the default namespace to use for assembly, field, or flag references
* that have no namespace prefix.
*
* @param uri
* the namespace URI
* @return this builder
* @see StaticContext#getDefaultFunctionNamespace()
*/
@NonNull
public Builder defaultFunctionNamespace(@NonNull URI uri) {
this.defaultFunctionNamespace = uri;
return this;
}
/**
* A convenience method for {@link #defaultFunctionNamespace(URI)}.
*
* @param uri
* the namespace URI
* @return this builder
* @throws IllegalArgumentException
* if the provided URI is invalid
* @see StaticContext#getDefaultFunctionNamespace()
*/
@NonNull
public Builder defaultFunctionNamespace(@NonNull String uri) {
return defaultFunctionNamespace(ObjectUtils.notNull(URI.create(uri)));
}
/**
* Set the name matching behavior for when a model node has no namespace.
*
* @param value
* {@code true} if on or {@code false} otherwise
* @return this builder
*/
public Builder useWildcardWhenNamespaceNotDefaulted(boolean value) {
this.useWildcardWhenNamespaceNotDefaulted = value;
return this;
}
/**
* Construct a new static context using the information provided to the builder.
*
* @return the new static context
*/
@NonNull
public StaticContext build() {
return new StaticContext(this);
}
}
}