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