1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath;
7   
8   import gov.nist.secauto.metaschema.core.datatype.DataTypeService;
9   import gov.nist.secauto.metaschema.core.metapath.function.FunctionService;
10  import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
11  import gov.nist.secauto.metaschema.core.metapath.function.IFunctionResolver;
12  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
14  import gov.nist.secauto.metaschema.core.metapath.type.IAtomicOrUnionType;
15  import gov.nist.secauto.metaschema.core.metapath.type.IItemType;
16  import gov.nist.secauto.metaschema.core.qname.EQNameFactory;
17  import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
18  import gov.nist.secauto.metaschema.core.qname.NamespaceCache;
19  import gov.nist.secauto.metaschema.core.qname.WellKnown;
20  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
21  import gov.nist.secauto.metaschema.core.util.CustomCollectors;
22  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
23  
24  import java.net.URI;
25  import java.net.URISyntaxException;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.stream.Collectors;
30  
31  import javax.xml.XMLConstants;
32  
33  import edu.umd.cs.findbugs.annotations.NonNull;
34  import edu.umd.cs.findbugs.annotations.Nullable;
35  
36  // add support for default namespace
37  /**
38   * An implementation of the Metapath
39   * <a href="https://www.w3.org/TR/xpath-31/#static_context">static context</a>.
40   */
41  // FIXME: refactor well-known into a new class
42  public final class StaticContext {
43    @Nullable
44    private final URI baseUri;
45    @NonNull
46    private final Map<String, String> knownPrefixToNamespace;
47    @NonNull
48    private final Map<String, String> knownNamespacesToPrefix;
49    @Nullable
50    private final String defaultModelNamespace;
51    @Nullable
52    private final String defaultFunctionNamespace;
53    private final boolean useWildcardWhenNamespaceNotDefaulted;
54    @NonNull
55    private final IFunctionResolver functionResolver;
56  
57    /**
58     * Get the mapping of prefix to namespace URI for all well-known namespaces
59     * provided by default to the static context.
60     * <p>
61     * These namespaces can be overridden using the
62     * {@link Builder#namespace(String, URI)} method.
63     * <p>
64     * This method has been deprecated. While
65     * {@link WellKnown#getWellKnownPrefixesToNamespaces()} can be used in place of
66     * this method, it is generally preferred to call
67     * {@link WellKnown#getWellKnownUriForPrefix(String)} instead.
68     *
69     * @return the mapping of prefix to namespace URI for all well-known namespaces
70     */
71    @NonNull
72    @Deprecated(since = "2.2.0", forRemoval = true)
73    public static Map<String, String> getWellKnownNamespacesMap() {
74      return WellKnown.getWellKnownPrefixesToNamespaces();
75    }
76  
77    /**
78     * Get the mapping of namespace URIs to prefixes for all well-known namespaces
79     * provided by default to the static context.
80     * <p>
81     * This method has been deprecated. While
82     * {@link WellKnown#getWellKnownURIsToPrefixes()} can be used in place of this
83     * method, it is generally preferred to call
84     * {@link WellKnown#getWellKnownPrefixForUri(String)} instead.
85     *
86     * @return the mapping of namespace URI to prefix for all well-known namespaces
87     */
88    @NonNull
89    @Deprecated(since = "2.2.0", forRemoval = true)
90    public static Map<String, String> getWellKnownURIToPrefixMap() {
91      return WellKnown.getWellKnownURIsToPrefixes();
92    }
93  
94    /**
95     * Get the namespace prefix associated with the provided URI, if the URI is
96     * well-known.
97     * <p>
98     * This method has been deprecated. While
99     * {@link WellKnown#getWellKnownPrefixForUri(String)} can be used in place of
100    * this method.
101    *
102    * @param uri
103    *          the URI to get the prefix for
104    * @return the prefix or {@code null} if the provided URI is not well-known
105    */
106   @Deprecated(since = "2.2.0", forRemoval = true)
107   @Nullable
108   public static String getWellKnownPrefixForUri(@NonNull String uri) {
109     return WellKnown.getWellKnownPrefixForUri(uri);
110   }
111 
112   /**
113    * Create a new static context instance using default values.
114    *
115    * @return a new static context instance
116    */
117   @NonNull
118   public static StaticContext instance() {
119     return builder().build();
120   }
121 
122   private StaticContext(Builder builder) {
123     this.baseUri = builder.baseUri;
124     this.knownPrefixToNamespace = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Map.copyOf(builder.namespaces)));
125     this.knownNamespacesToPrefix = ObjectUtils.notNull(builder.namespaces.entrySet().stream()
126         .map(entry -> Map.entry(entry.getValue(), entry.getKey()))
127         .collect(Collectors.toUnmodifiableMap(
128             Map.Entry::getKey,
129             Map.Entry::getValue,
130             CustomCollectors.useFirstMapper())));
131     this.defaultModelNamespace = builder.defaultModelNamespace;
132     this.defaultFunctionNamespace = builder.defaultFunctionNamespace;
133     this.useWildcardWhenNamespaceNotDefaulted = builder.useWildcardWhenNamespaceNotDefaulted;
134     this.functionResolver = builder.functionResolver;
135   }
136 
137   /**
138    * Get the static base URI to use in resolving URIs handled by the Metapath
139    * processor. This URI, if provided, will be used when a document base URI is
140    * not available.
141    *
142    * @return the base URI or {@code null} if not defined
143    */
144   @Nullable
145   public URI getBaseUri() {
146     return baseUri;
147   }
148 
149   /**
150    * Get the namespace URI associated with the provided {@code prefix}, if any is
151    * bound.
152    * <p>
153    * This method uses the namespaces set by the
154    * {@link Builder#namespace(String, URI)} method, falling back to the well-known
155    * namespace bindings when a prefix match is not found.
156    * <p>
157    * The well-known namespace bindings can be retrieved using the
158    * {@link StaticContext#getWellKnownNamespacesMap()} method.
159    *
160    * @param prefix
161    *          the namespace prefix
162    * @return the namespace URI bound to the prefix, or {@code null} if no
163    *         namespace is bound to the prefix
164    * @see Builder#namespace(String, URI)
165    * @see #getWellKnownNamespacesMap()
166    */
167   @Nullable
168   private String lookupNamespaceURIForPrefix(@NonNull String prefix) {
169     String retval = knownPrefixToNamespace.get(prefix);
170     if (retval == null) {
171       // fall back to well-known namespaces
172       retval = WellKnown.getWellKnownUriForPrefix(prefix);
173     }
174     return retval;
175   }
176 
177   @Nullable
178   private String lookupPrefixForNamespaceURI(@NonNull String namespace) {
179     String retval = knownNamespacesToPrefix.get(namespace);
180     if (retval == null) {
181       // fall back to well-known namespaces
182       retval = WellKnown.getWellKnownPrefixForUri(namespace);
183     }
184     return retval;
185   }
186 
187   /**
188    * Get the namespace associated with the provided {@code prefix} as a string, if
189    * any is bound.
190    *
191    * @param prefix
192    *          the namespace prefix
193    * @return the namespace string bound to the prefix, or {@code null} if no
194    *         namespace is bound to the prefix
195    */
196   @Nullable
197   public String lookupNamespaceForPrefix(@NonNull String prefix) {
198     String result = lookupNamespaceURIForPrefix(prefix);
199     return result == null ? null : result;
200   }
201 
202   /**
203    * Get the prefix associated with the provided {@code namespace} as a string, if
204    * any is bound.
205    *
206    * @param namespace
207    *          the namespace
208    * @return the prefix string bound to the prefix, or {@code null} if no prefix
209    *         is bound to the namespace
210    */
211   @Nullable
212   public String lookupPrefixForNamespace(@NonNull String namespace) {
213     return lookupPrefixForNamespaceURI(namespace);
214   }
215 
216   /**
217    * Get the default namespace for assembly, field, or flag references that have
218    * no namespace prefix.
219    *
220    * @return the namespace if defined or {@code null} otherwise
221    */
222   @Nullable
223   private String getDefaultModelNamespace() {
224     return defaultModelNamespace;
225   }
226 
227   /**
228    * Get the default namespace for function references that have no namespace
229    * prefix.
230    *
231    * @return the namespace if defined or {@code null} otherwise
232    */
233   @Nullable
234   private String getDefaultFunctionNamespace() {
235     return defaultFunctionNamespace;
236   }
237 
238   /**
239    * Parse the name of an atomic type.
240    * <p>
241    * This method will attempt to identify the namespace corresponding to a given
242    * prefix.
243    * <p>
244    * The prefix will be resolved using the following lookup order, advancing to
245    * the next when a {@code null} value is returned:
246    * <ol>
247    * <li>Lookup the prefix using the namespaces registered with the static
248    * context.
249    * <li>Lookup the prefix in the well-known namespaces.
250    * </ol>
251    * <p>
252    * If an empty prefix is provided, the {@link MetapathConstants#NS_METAPATH}
253    * namespace will be used.
254    *
255    * @param name
256    *          the name
257    * @return the parsed qualified name
258    * @throws StaticMetapathException
259    *           with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
260    *           if a non-empty prefix is provided
261    */
262   @NonNull
263   public IEnhancedQName parseAtomicTypeName(@NonNull String name) {
264     return EQNameFactory.instance().parseName(
265         name,
266         this::resolveAtomicTypePrefix);
267   }
268 
269   private String resolveAtomicTypePrefix(@NonNull String prefix) {
270     String ns = lookupNamespaceForPrefix(prefix);
271     if (ns == null) {
272       checkForUnknownPrefix(prefix);
273       // use the default data type namespace
274       ns = MetapathConstants.NS_METAPATH;
275     }
276     return ns;
277   }
278 
279   /**
280    * Lookup the atomic type with the provided name in the static context.
281    * <p>
282    * This method will first attempt to expand the namespace prefix for a lexical
283    * QName. A {@link StaticMetapathException} with the code
284    * {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE} if the prefix is not
285    * known to the static context.
286    * <p>
287    * Once the qualified name has been produced, the atomic type will be retrieved
288    * from the available atomic types. If the atomic type was not found, a
289    * {@link StaticMetapathException} with the code
290    * {@link StaticMetapathException#UNKNOWN_TYPE} will be thrown. Otherwise, the
291    * type information is returned for the matching atomic type.
292    *
293    * @param name
294    *          the namespace qualified or lexical name of the data type.
295    * @return the data type information
296    * @throws StaticMetapathException
297    *           with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
298    *           if the lexical name was not able to be expanded or the code
299    *           {@link StaticMetapathException#NO_FUNCTION_MATCH} if a matching
300    *           type was not found
301    */
302   @NonNull
303   public IAtomicOrUnionType<?> lookupAtomicType(@NonNull String name) {
304     IEnhancedQName qname = parseAtomicTypeName(name);
305     return lookupAtomicType(qname);
306   }
307 
308   /**
309    * Lookup a known Metapath atomic type based on the type's qualified name.
310    *
311    * @param qname
312    *          the qualified name
313    * @return the type
314    * @throws StaticMetapathException
315    *           with the code {@link StaticMetapathException#UNKNOWN_TYPE} if the
316    *           type was not found
317    */
318   @NonNull
319   public static IAtomicOrUnionType<?> lookupAtomicType(@NonNull IEnhancedQName qname) {
320     IAtomicOrUnionType<?> retval = DataTypeService.instance().getAtomicTypeByQNameIndex(qname.getIndexPosition());
321     if (retval == null) {
322       throw new StaticMetapathException(
323           StaticMetapathException.UNKNOWN_TYPE,
324           String.format("The atomic type named '%s' was not found.", qname));
325     }
326     return retval;
327   }
328 
329   /**
330    * Lookup a known Metapath atomic type based on the type's item class.
331    *
332    * @param <T>
333    *          the Java type of the item to get the type information for
334    * @param clazz
335    *          the item class associated with the atomic type
336    * @return the type
337    * @throws StaticMetapathException
338    *           with the code {@link StaticMetapathException#UNKNOWN_TYPE} if the
339    *           type was not found
340    */
341   @NonNull
342   public static <T extends IAnyAtomicItem> IAtomicOrUnionType<T> lookupAtomicType(Class<T> clazz) {
343     IAtomicOrUnionType<T> retval = DataTypeService.instance().getAtomicTypeByItemClass(clazz);
344     if (retval == null) {
345       throw new StaticMetapathException(
346           StaticMetapathException.UNKNOWN_TYPE,
347           String.format("The atomic type for item class '%s' was not found.", clazz.getName()));
348     }
349     return retval;
350   }
351 
352   /**
353    * Lookup a known Metapath item type based on the type's item class.
354    *
355    * @param clazz
356    *          the item class associated with the atomic type
357    * @return the type
358    * @throws StaticMetapathException
359    *           with the code {@link StaticMetapathException#UNKNOWN_TYPE} if the
360    *           type was not found
361    */
362   @NonNull
363   public static IItemType lookupItemType(Class<? extends IItem> clazz) {
364     IItemType retval = DataTypeService.instance().getItemTypeByItemClass(clazz);
365     if (retval == null) {
366       throw new StaticMetapathException(
367           StaticMetapathException.UNKNOWN_TYPE,
368           String.format("The item type for item class '%s' was not found.", clazz.getName()));
369     }
370     return retval;
371   }
372 
373   /**
374    * Parse a function name.
375    * <p>
376    * This method will attempt to identify the namespace corresponding to a given
377    * prefix.
378    * <p>
379    * The prefix will be resolved using the following lookup order, advancing to
380    * the next when a {@code null} value is returned:
381    * <ol>
382    * <li>Lookup the prefix using the namespaces registered with the static
383    * context.
384    * <li>Lookup the prefix in the well-known namespaces.
385    * </ol>
386    * If an empty prefix is provided, the
387    * {@link Builder#defaultFunctionNamespace(String)} namespace will be used.
388    *
389    * @param name
390    *          the name
391    * @return the parsed qualified name
392    */
393   @NonNull
394   public IEnhancedQName parseFunctionName(@NonNull String name) {
395     return EQNameFactory.instance().parseName(
396         name,
397         this::resolveFunctionPrefix);
398   }
399 
400   @NonNull
401   private String resolveFunctionPrefix(@NonNull String prefix) {
402     String ns = lookupNamespaceForPrefix(prefix);
403     if (ns == null) {
404       checkForUnknownPrefix(prefix);
405       // use the default namespace, since the namespace was omitted
406       ns = getDefaultFunctionNamespace();
407     }
408     return ns == null ? XMLConstants.NULL_NS_URI : ns;
409   }
410 
411   /**
412    * Checks if the provided prefix is not-empty, which means the prefix was not
413    * resolvable.
414    *
415    * @param prefix
416    *          the lexical prefix to check
417    * @throws StaticMetapathException
418    *           with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
419    *           if a non-empty prefix is provided
420    */
421   private static void checkForUnknownPrefix(@NonNull String prefix) {
422     if (!prefix.isEmpty()) {
423       throw new StaticMetapathException(
424           StaticMetapathException.PREFIX_NOT_EXPANDABLE,
425           String.format("The namespace prefix '%s' is not expandable.",
426               prefix));
427     }
428   }
429 
430   /**
431    * Lookup a known Metapath function based on the function's name and arity.
432    * <p>
433    * This method will first attempt to expand the namespace prefix for a lexical
434    * QName. A {@link StaticMetapathException} with the code
435    * {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE} if the prefix is not
436    * known to the static context.
437    * <p>
438    * Once the qualified name has been produced, the function will be retrieved
439    * from the available functions. If the function was not found, a
440    * {@link StaticMetapathException} with the code
441    * {@link StaticMetapathException#UNKNOWN_TYPE} will be thrown. Otherwise, the
442    * data type information is returned for the matching data type.
443    *
444    * @param name
445    *          the qualified or lexical name of the function
446    * @param arity
447    *          the number of arguments
448    * @return the type
449    * @throws StaticMetapathException
450    *           with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
451    *           if the lexical name was not able to be expanded or the code
452    *           {@link StaticMetapathException#NO_FUNCTION_MATCH} if a matching
453    *           function was not found
454    */
455   @NonNull
456   public IFunction lookupFunction(@NonNull String name, int arity) {
457     IEnhancedQName qname = parseFunctionName(name);
458     return functionResolver.getFunction(qname, arity);
459   }
460 
461   /**
462    * Lookup a known Metapath function based on the function's name and arity.
463    *
464    * @param qname
465    *          the qualified name of the function
466    * @param arity
467    *          the number of arguments
468    * @return the function
469    * @throws StaticMetapathException
470    *           with the code {@link StaticMetapathException#NO_FUNCTION_MATCH} if
471    *           a matching function was not found
472    */
473   @NonNull
474   public static IFunction lookupFunction(@NonNull IEnhancedQName qname, int arity) {
475     return FunctionService.getInstance().getFunction(
476         Objects.requireNonNull(qname, "name"),
477         arity);
478   }
479 
480   /**
481    * Parse a flag name.
482    * <p>
483    * This method will attempt to identify the namespace corresponding to a given
484    * prefix.
485    * <p>
486    * The prefix will be resolved using the following lookup order, advancing to
487    * the next when a {@code null} value is returned:
488    * <ol>
489    * <li>Lookup the prefix using the namespaces registered with the static
490    * context.
491    * <li>Lookup the prefix in the well-known namespaces.
492    * </ol>
493    * If an empty prefix is provided, the {@link XMLConstants#NULL_NS_URI}
494    * namespace will be used.
495    *
496    * @param name
497    *          the name
498    * @return the parsed qualified name
499    * @throws StaticMetapathException
500    *           with the code {@link StaticMetapathException#PREFIX_NOT_EXPANDABLE}
501    *           if a non-empty prefix is provided
502    */
503   @NonNull
504   public IEnhancedQName parseFlagName(@NonNull String name) {
505     return EQNameFactory.instance().parseName(
506         name,
507         this::resolveBasicPrefix);
508   }
509 
510   private String resolveBasicPrefix(@NonNull String prefix) {
511     String ns = lookupNamespaceForPrefix(prefix);
512     if (ns == null) {
513       checkForUnknownPrefix(prefix);
514     }
515     return ns == null ? XMLConstants.NULL_NS_URI : ns;
516   }
517 
518   /**
519    * Parse a model name.
520    * <p>
521    * This method will attempt to identify the namespace corresponding to a given
522    * prefix.
523    * <p>
524    * The prefix will be resolved using the following lookup order, advancing to
525    * the next when a {@code null} value is returned:
526    * <ol>
527    * <li>Lookup the prefix using the namespaces registered with the static
528    * context.
529    * <li>Lookup the prefix in the well-known namespaces.
530    * </ol>
531    * If an empty prefix is provided, the
532    * {@link Builder#defaultModelNamespace(String)} namespace will be used.
533    *
534    * @param name
535    *          the name
536    * @return the parsed qualified name
537    */
538   @NonNull
539   public IEnhancedQName parseModelName(@NonNull String name) {
540     return EQNameFactory.instance().parseName(
541         name,
542         this::resolveModelReferencePrefix);
543   }
544 
545   @NonNull
546   private String resolveModelReferencePrefix(@NonNull String prefix) {
547     String ns = lookupNamespaceForPrefix(prefix);
548     if (ns == null) {
549       checkForUnknownPrefix(prefix);
550       ns = getDefaultModelNamespace();
551     }
552     return ns == null ? XMLConstants.NULL_NS_URI : ns;
553   }
554 
555   /**
556    * Parse a variable name.
557    * <p>
558    * This method will attempt to identify the namespace corresponding to a given
559    * prefix.
560    * <p>
561    * The prefix will be resolved using the following lookup order, advancing to
562    * the next when a {@code null} value is returned:
563    * <ol>
564    * <li>Lookup the prefix using the namespaces registered with the static
565    * context.
566    * <li>Lookup the prefix in the well-known namespaces.
567    * </ol>
568    * If an empty prefix is provided, the {@link XMLConstants#NULL_NS_URI}
569    * namespace will be used.
570    *
571    * @param name
572    *          the name
573    * @return the parsed qualified name
574    */
575   @NonNull
576   public IEnhancedQName parseVariableName(@NonNull String name) {
577     return EQNameFactory.instance().parseName(
578         name,
579         this::resolveBasicPrefix);
580   }
581 
582   /**
583    * Get a new static context builder that is pre-populated with the setting of
584    * this static context.
585    *
586    * @return a new builder
587    */
588   @NonNull
589   public Builder buildFrom() {
590     Builder builder = builder();
591     builder.baseUri = this.baseUri;
592     builder.namespaces.putAll(this.knownPrefixToNamespace);
593     builder.defaultModelNamespace = this.defaultModelNamespace;
594     builder.defaultFunctionNamespace = this.defaultFunctionNamespace;
595     return builder;
596   }
597 
598   /**
599    * Indicates if a name match should use a wildcard for the namespace if the
600    * namespace does not have a value and the default model namespace is
601    * {@code null}.
602    *
603    * @return {@code true} if a wildcard match on the name space should be used or
604    *         {@code false} otherwise
605    */
606   public boolean isUseWildcardWhenNamespaceNotDefaulted() {
607     return useWildcardWhenNamespaceNotDefaulted && getDefaultModelNamespace() == null;
608   }
609 
610   /**
611    * Create a new static context builder that allows for fine-grained adjustments
612    * when creating a new static context.
613    *
614    * @return a new builder
615    */
616   @NonNull
617   public static Builder builder() {
618     return new Builder();
619   }
620 
621   /**
622    * A builder used to generate the static context.
623    */
624   public static final class Builder {
625     private boolean useWildcardWhenNamespaceNotDefaulted; // false
626     @Nullable
627     private URI baseUri;
628     @NonNull
629     private final Map<String, String> namespaces = new ConcurrentHashMap<>();
630     @Nullable
631     private String defaultModelNamespace;
632     @Nullable
633     private String defaultFunctionNamespace = MetapathConstants.NS_METAPATH_FUNCTIONS;
634     @NonNull
635     private IFunctionResolver functionResolver = FunctionService.getInstance();
636 
637     private Builder() {
638       // avoid direct construction
639     }
640 
641     /**
642      * Sets the static base URI to use in resolving URIs handled by the Metapath
643      * processor, when a document base URI is not available. There is only a single
644      * base URI. Subsequent calls to this method will change the base URI.
645      *
646      * @param uri
647      *          the base URI to use
648      * @return this builder
649      */
650     @NonNull
651     public Builder baseUri(@NonNull URI uri) {
652       this.baseUri = uri;
653       return this;
654     }
655 
656     /**
657      * Adds a new prefix to namespace URI binding to the mapping of
658      * <a href="https://www.w3.org/TR/xpath-31/#dt-static-namespaces">statically
659      * known namespaces</a>.
660      * <p>
661      * A namespace set by this method can be resolved using the
662      * {@link StaticContext#lookupNamespaceForPrefix(String)} method.
663      * <p>
664      * Well-known namespace bindings are used by default, which are provided by
665      * {@link WellKnown}.
666      *
667      * @param prefix
668      *          the prefix to associate with the namespace, which may be
669      * @param uri
670      *          the namespace URI
671      * @return this builder
672      * @see StaticContext#lookupNamespaceForPrefix(String)
673      */
674     @NonNull
675     public Builder namespace(@NonNull String prefix, @NonNull URI uri) {
676       return namespace(prefix, ObjectUtils.notNull(uri.toASCIIString()));
677     }
678 
679     /**
680      * A convenience method for {@link #namespace(String, URI)}.
681      *
682      * @param prefix
683      *          the prefix to associate with the namespace, which may be
684      * @param uri
685      *          the namespace URI
686      * @return this builder
687      * @throws IllegalArgumentException
688      *           if the provided prefix or URI is invalid
689      * @see StaticContext#lookupNamespaceForPrefix(String)
690      */
691     @NonNull
692     public Builder namespace(@NonNull String prefix, @NonNull String uri) {
693       if (MetapathConstants.PREFIX_METAPATH.equals(prefix)) {
694         // check for https://www.w3.org/TR/xpath-31/#ERRXPST0070 for "meta"
695         throw new IllegalArgumentException(
696             "Redefining the prefix '" + MetapathConstants.PREFIX_METAPATH + "' is not allowed.");
697       }
698       this.namespaces.put(prefix, uri);
699       NamespaceCache.instance().indexOf(uri);
700       return this;
701     }
702 
703     /**
704      * Defines the default namespace to use for assembly, field, or flag references
705      * that have no namespace prefix.
706      *
707      * @param namespace
708      *          the namespace URI
709      * @return this builder
710      */
711     @NonNull
712     public Builder defaultModelNamespace(@NonNull URI namespace) {
713       String uri = ObjectUtils.notNull(namespace.toASCIIString());
714       this.defaultModelNamespace = uri;
715       NamespaceCache.instance().indexOf(uri);
716       return this;
717     }
718 
719     /**
720      * A convenience method for {@link #defaultModelNamespace(URI)}.
721      *
722      * @param uri
723      *          the namespace URI
724      * @return this builder
725      * @throws IllegalArgumentException
726      *           if the provided URI is invalid
727      */
728     @NonNull
729     public Builder defaultModelNamespace(@NonNull String uri) {
730       try {
731         this.defaultModelNamespace = new URI(uri).toASCIIString();
732       } catch (URISyntaxException ex) {
733         throw new IllegalArgumentException(ex);
734       }
735       NamespaceCache.instance().indexOf(uri);
736       return this;
737     }
738 
739     /**
740      * Defines the default namespace to use for assembly, field, or flag references
741      * that have no namespace prefix.
742      *
743      * @param namespace
744      *          the namespace URI
745      * @return this builder
746      */
747     @NonNull
748     public Builder defaultFunctionNamespace(@NonNull URI namespace) {
749       String uri = ObjectUtils.notNull(namespace.toASCIIString());
750       this.defaultFunctionNamespace = uri;
751       NamespaceCache.instance().indexOf(uri);
752       return this;
753     }
754 
755     /**
756      * A convenience method for {@link #defaultFunctionNamespace(URI)}.
757      *
758      * @param uri
759      *          the namespace URI
760      * @return this builder
761      * @throws IllegalArgumentException
762      *           if the provided URI is invalid
763      */
764     @NonNull
765     public Builder defaultFunctionNamespace(@NonNull String uri) {
766       try {
767         this.defaultFunctionNamespace = new URI(uri).toASCIIString();
768       } catch (URISyntaxException ex) {
769         throw new IllegalArgumentException(ex);
770       }
771       NamespaceCache.instance().indexOf(uri);
772       return this;
773     }
774 
775     /**
776      * Set the name matching behavior for when a model node has no namespace.
777      *
778      * @param value
779      *          {@code true} if on or {@code false} otherwise
780      * @return this builder
781      */
782     public Builder useWildcardWhenNamespaceNotDefaulted(boolean value) {
783       this.useWildcardWhenNamespaceNotDefaulted = value;
784       return this;
785     }
786 
787     /**
788      * Set the function resolver used to lookup function implementations.
789      * <p>
790      * By default, the {@link FunctionService} is used to load function
791      * implementations using the service provider interface.
792      *
793      * @param resolver
794      *          the resolver to use instead of the default resolver
795      * @return this builder
796      */
797     public Builder functionResolver(@NonNull IFunctionResolver resolver) {
798       this.functionResolver = resolver;
799       return this;
800     }
801 
802     /**
803      * Construct a new static context using the information provided to the builder.
804      *
805      * @return the new static context
806      */
807     @NonNull
808     public StaticContext build() {
809       return new StaticContext(this);
810     }
811   }
812 }