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 }