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