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