1 /* 2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.xml.catalog; 26 27 import java.net.MalformedURLException; 28 import java.net.URISyntaxException; 29 import java.util.HashMap; 30 import java.util.Map; 31 import jdk.xml.internal.SecuritySupport; 32 33 /** 34 * The CatalogFeatures holds a collection of features and properties. 35 * 36 * 37 * <table class="plain" id="CatalogFeatures"> 38 * <caption>Catalog Features</caption> 39 * <thead> 40 * <tr> 41 * <th scope="col" rowspan="2">Feature</th> 42 * <th scope="col" rowspan="2">Description</th> 43 * <th scope="col" rowspan="2">Property Name</th> 44 * <th scope="col" rowspan="2">System Property [1]</th> 45 * <th scope="col" rowspan="2">jaxp.properties [1]</th> 46 * <th scope="col" colspan="2" style="text-align:center">Value [2]</th> 47 * <th scope="col" rowspan="2">Action</th> 48 * </tr> 49 * <tr> 50 * <th scope="col">Type</th> 51 * <th scope="col">Value</th> 52 * </tr> 53 * </thead> 54 * 55 * <tbody> 56 * 57 * <tr> 58 * <th scope="row" style="font-weight:normal" id="FILES">FILES</th> 59 * <td>A semicolon-delimited list of URIs to locate the catalog files. 60 * The URIs must be absolute and have a URL protocol handler for the URI scheme. 61 * </td> 62 * <td>javax.xml.catalog.files</td> 63 * <td>javax.xml.catalog.files</td> 64 * <td>javax.xml.catalog.files</td> 65 * <td>String</td> 66 * <th id="URIs" scope="row" style="font-weight:normal">URIs</th> 67 * <td> 68 * Reads the first catalog as the current catalog; Loads others if no match 69 * is found in the current catalog including delegate catalogs if any. 70 * </td> 71 * </tr> 72 * 73 * <tr> 74 * <th rowspan="2" scope="row" style="font-weight:normal" id="PREFER">PREFER</th> 75 * <td rowspan="2">Indicates the preference between the public and system 76 * identifiers. The default value is public [3].</td> 77 * <td rowspan="2">javax.xml.catalog.prefer</td> 78 * <td rowspan="2">N/A</td> 79 * <td rowspan="2">N/A</td> 80 * <td rowspan="2">String</td> 81 * <th scope="row" id="system" style="font-weight:normal">{@code system}</th> 82 * <td> 83 * Searches system entries for a match; Searches public entries when 84 * external identifier specifies only a public identifier</td> 85 * </tr> 86 * <tr> 87 * <th scope="row" id="public" style="font-weight:normal">{@code public}</th> 88 * <td> 89 * Searches system entries for a match; Searches public entries when 90 * there is no matching system entry.</td> 91 * </tr> 92 * 93 * <tr> 94 * <th rowspan="2" scope="row" style="font-weight:normal" id="DEFER">DEFER</th> 95 * <td rowspan="2">Indicates that the alternative catalogs including those 96 * specified in delegate entries or nextCatalog are not read until they are 97 * needed. The default value is true.</td> 98 * <td rowspan="2">javax.xml.catalog.defer [4]</td> 99 * <td rowspan="2">javax.xml.catalog.defer</td> 100 * <td rowspan="2">javax.xml.catalog.defer</td> 101 * <td rowspan="2">String</td> 102 * <th scope="row" id="true" style="font-weight:normal">{@code true}</th> 103 * <td> 104 * Loads alternative catalogs as needed. 105 * </td> 106 * </tr> 107 * <tr> 108 * <th scope="row" id="false" style="font-weight:normal">{@code false}</th> 109 * <td> 110 * Loads all catalogs[5]. </td> 111 * </tr> 112 * 113 * <tr> 114 * <th rowspan="3" scope="row" style="font-weight:normal" id="RESOLVE">RESOLVE</th> 115 * <td rowspan="3">Determines the action if there is no matching entry found after 116 * all of the specified catalogs are exhausted. The default is strict.</td> 117 * <td rowspan="3">javax.xml.catalog.resolve [4]</td> 118 * <td rowspan="3">javax.xml.catalog.resolve</td> 119 * <td rowspan="3">javax.xml.catalog.resolve</td> 120 * <td rowspan="3">String</td> 121 * <th scope="row" id="strict" style="font-weight:normal">{@code strict}</th> 122 * <td> 123 * Throws CatalogException if there is no match. 124 * </td> 125 * </tr> 126 * <tr> 127 * <th scope="row" id="continue" style="font-weight:normal">{@code continue}</th> 128 * <td> 129 * Allows the XML parser to continue as if there is no match. 130 * </td> 131 * </tr> 132 * <tr> 133 * <th scope="row" id="ignore" style="font-weight:normal">{@code ignore}</th> 134 * <td> 135 * Tells the XML parser to skip the external references if there no match. 136 * </td> 137 * </tr> 138 * 139 * </tbody> 140 * </table> 141 * <p> 142 * <b>[1]</b> There is no System property for the features that marked as "N/A". 143 * 144 * <p> 145 * <b>[2]</b> The value shall be exactly as listed in this table, case-sensitive. 146 * Any unspecified value will result in {@link IllegalArgumentException}. 147 * <p> 148 * <b>[3]</b> The Catalog specification defined complex rules on 149 * <a href="https://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html#attrib.prefer"> 150 * the prefer attribute</a>. Although the prefer can be public or system, the 151 * specification actually made system the preferred option, that is, no matter 152 * the option, a system entry is always used if found. Public entries are only 153 * considered if the prefer is public and system entries are not found. It is 154 * therefore recommended that the prefer attribute be set as public 155 * (which is the default). 156 * <p> 157 * <b>[4]</b> Although non-standard attributes in the OASIS Catalog specification, 158 * {@code defer} and {@code resolve} are recognized by the Java Catalog API the 159 * same as the {@code prefer} as being an attribute in the catalog entry of the 160 * main catalog. Note that only the attributes specified for the catalog entry 161 * of the main Catalog file will be used. 162 * <p> 163 * <b>[5]</b> If the intention is to share an entire catalog store, it may be desirable to 164 * set the property {@code javax.xml.catalog.defer} to false to allow the entire 165 * catalog to be pre-loaded. 166 * 167 * <h3>Scope and Order</h3> 168 * Features and properties can be set through the catalog file, the Catalog API, 169 * system properties, and {@code jaxp.properties}, with a preference in the same order. 170 * <p> 171 * Properties that are specified as attributes in the catalog file for the 172 * catalog and group entries shall take preference over any of the other settings. 173 * For example, if a {@code prefer} attribute is set in the catalog file as in 174 * {@code <catalog prefer="public">}, any other input for the "prefer" property 175 * is not necessary or will be ignored. 176 * <p> 177 * Properties set through the Catalog API override those that may have been set 178 * by system properties and/or in {@code jaxp.properties}. In case of multiple 179 * interfaces, the latest in a procedure shall take preference. For 180 * {@link Feature#FILES}, this means that the URI(s) specified through the methods 181 * of the {@link CatalogManager} will override any that may have been entered 182 * through the {@link Builder}. 183 * 184 * <p> 185 * System properties when set shall override those in {@code jaxp.properties}. 186 * <p> 187 * The {@code jaxp.properties} file is typically in the conf directory of the Java 188 * installation. The file is read only once by the JAXP implementation and 189 * its values are then cached for future use. If the file does not exist 190 * when the first attempt is made to read from it, no further attempts are 191 * made to check for its existence. It is not possible to change the value 192 * of any properties in {@code jaxp.properties} after it has been read. 193 * <p> 194 * A CatalogFeatures instance can be created through its builder as illustrated 195 * in the following sample code: 196 * <pre>{@code 197 CatalogFeatures f = CatalogFeatures.builder() 198 .with(Feature.FILES, "file:///etc/xml/catalog") 199 .with(Feature.PREFER, "public") 200 .with(Feature.DEFER, "true") 201 .with(Feature.RESOLVE, "ignore") 202 .build(); 203 * }</pre> 204 * 205 * <h3>JAXP XML Processor Support</h3> 206 * The Catalog Features are supported throughout the JAXP processors, including 207 * SAX and DOM ({@link javax.xml.parsers}), and StAX parsers ({@link javax.xml.stream}), 208 * Schema Validation ({@link javax.xml.validation}), and XML Transformation 209 * ({@link javax.xml.transform}). The features described above can be set through JAXP 210 * factories or processors that define a setProperty or setAttribute interface. 211 * For example, the following code snippet sets a URI to a catalog file on a SAX 212 * parser through the {@code javax.xml.catalog.files} property: 213 * 214 * <pre>{@code 215 * SAXParserFactory spf = SAXParserFactory.newInstance(); 216 * spf.setFeature(XMLConstants.USE_CATALOG, true); [1] 217 * SAXParser parser = spf.newSAXParser(); 218 * parser.setProperty(CatalogFeatures.Feature.FILES.getPropertyName(), "file:///etc/xml/catalog"); 219 * }</pre> 220 * <p> 221 * [1] Note that this statement is not required since the default value of 222 * {@link javax.xml.XMLConstants#USE_CATALOG USE_CATALOG} is true. 223 * 224 * <p> 225 * The JAXP Processors' support for Catalog depends on both the 226 * {@link javax.xml.XMLConstants#USE_CATALOG USE_CATALOG} feature and the 227 * existence of valid Catalog file(s). A JAXP processor will use the Catalog 228 * only when the feature is true and valid Catalog file(s) are specified through 229 * the {@code javax.xml.catalog.files} property. It will make no attempt to use 230 * the Catalog if either {@link javax.xml.XMLConstants#USE_CATALOG USE_CATALOG} 231 * is set to false, or there is no Catalog file specified. 232 * 233 * <p> 234 * The JAXP processors will observe the default settings of the 235 * {@link javax.xml.catalog.CatalogFeatures}. The processors, for example, will 236 * report an Exception by default when no matching entry is found since the 237 * default value of the {@code javax.xml.catalog.resolve} property is strict. 238 * 239 * <p> 240 * The JAXP processors give preference to user-specified custom resolvers. If such 241 * a resolver is registered, it will be used over the CatalogResolver. If it returns 242 * null however, the processors will continue resolving with the CatalogResolver. 243 * If it returns an empty source, no attempt will be made by the CatalogResolver. 244 * 245 * <p> 246 * The Catalog support is available for any process in the JAXP library that 247 * supports a resolver. The following table lists all such processes. 248 * 249 * <h3><a id="ProcessesWithCatalogSupport">Processes with Catalog Support</a></h3> 250 * 251 * <table class="striped"> 252 * <caption>Processes with Catalog Support</caption> 253 * <thead> 254 * <tr> 255 * <th scope="col">Process</th> 256 * <th scope="col">Catalog Entry Type</th> 257 * <th scope="col">Example</th> 258 * </tr> 259 * </thead> 260 * <tbody> 261 * <tr> 262 * <th scope="row">DTDs and external entities</th> 263 * <td>public, system</td> 264 * <td> 265 * <pre>{@literal 266 The following DTD reference: 267 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 268 269 Can be resolved using the following Catalog entry: 270 <public publicId="-//W3C//DTD XHTML 1.0 Strict//EN" uri="catalog/xhtml1-strict.dtd"/> 271 or 272 <systemSuffix systemIdSuffix="html1-strict.dtd" uri="catalog/xhtml1-strict.dtd"/> 273 * }</pre> 274 * </td> 275 * </tr> 276 * <tr> 277 * <th scope="row">XInclude</th> 278 * <td>uri</td> 279 * <td> 280 * <pre>{@literal 281 The following XInclude element: 282 <xi:include href="http://openjdk.java.net/xml/disclaimer.xml"/> 283 284 can be resolved using a URI entry: 285 <uri name="http://openjdk.java.net/xml/disclaimer.xml" uri="file:///pathto/local/disclaimer.xml"/> 286 or 287 <uriSuffix uriSuffix="disclaimer.xml" uri="file:///pathto/local/disclaimer.xml"/> 288 * }</pre> 289 * </td> 290 * </tr> 291 * <tr> 292 * <th scope="row">XSD import</th> 293 * <td>uri</td> 294 * <td> 295 * <pre>{@literal 296 The following import element: 297 <xsd:import namespace="http://openjdk.java.net/xsd/XSDImport_person" 298 schemaLocation="http://openjdk.java.net/xsd/XSDImport_person.xsd"/> 299 300 can be resolved using a URI entry: 301 <uri name="http://openjdk.java.net/xsd/XSDImport_person.xsd" uri="file:///pathto/local/XSDImport_person.xsd"/> 302 or 303 <uriSuffix uriSuffix="XSDImport_person.xsd" uri="file:///pathto/local/XSDImport_person.xsd"/> 304 or 305 <uriSuffix uriSuffix="http://openjdk.java.net/xsd/XSDImport_person" uri="file:///pathto/local/XSDImport_person.xsd"/> 306 * }</pre> 307 * </td> 308 * </tr> 309 * <tr> 310 * <th scope="row">XSD include</th> 311 * <td>uri</td> 312 * <td> 313 * <pre>{@literal 314 The following include element: 315 <xsd:include schemaLocation="http://openjdk.java.net/xsd/XSDInclude_person.xsd"/> 316 317 can be resolved using a URI entry: 318 <uri name="http://openjdk.java.net/xsd/XSDInclude_person.xsd" uri="file:///pathto/local/XSDInclude_person.xsd"/> 319 or 320 <uriSuffix uriSuffix="XSDInclude_person.xsd" uri="file:///pathto/local/XSDInclude_person.xsd"/> 321 * }</pre> 322 * </td> 323 * </tr> 324 * <tr> 325 * <th scope="row">XSL import and include</th> 326 * <td>uri</td> 327 * <td> 328 * <pre>{@literal 329 The following include element: 330 <xsl:include href="http://openjdk.java.net/xsl/include.xsl"/> 331 332 can be resolved using a URI entry: 333 <uri name="http://openjdk.java.net/xsl/include.xsl" uri="file:///pathto/local/include.xsl"/> 334 or 335 <uriSuffix uriSuffix="include.xsl" uri="file:///pathto/local/include.xsl"/> 336 * }</pre> 337 * </td> 338 * </tr> 339 * <tr> 340 * <th scope="row">XSL document function</th> 341 * <td>uri</td> 342 * <td> 343 * <pre>{@literal 344 The document in the following element: 345 <xsl:variable name="dummy" select="document('http://openjdk.java.net/xsl/list.xml')"/> 346 347 can be resolved using a URI entry: 348 <uri name="http://openjdk.java.net/xsl/list.xml" uri="file:///pathto/local/list.xml"/> 349 or 350 <uriSuffix uriSuffix="list.xml" uri="file:///pathto/local/list.xml"/> 351 * }</pre> 352 * </td> 353 * </tr> 354 * </tbody> 355 * </table> 356 * 357 * @since 9 358 */ 359 public class CatalogFeatures { 360 361 /** 362 * The constant name of the javax.xml.catalog.files property as described 363 * in the property table above. 364 */ 365 static final String CATALOG_FILES = "javax.xml.catalog.files"; 366 367 /** 368 * The javax.xml.catalog.prefer property as described 369 * in the property table above. 370 */ 371 static final String CATALOG_PREFER = "javax.xml.catalog.prefer"; 372 373 /** 374 * The javax.xml.catalog.defer property as described 375 * in the property table above. 376 */ 377 static final String CATALOG_DEFER = "javax.xml.catalog.defer"; 378 379 /** 380 * The javax.xml.catalog.resolve property as described 381 * in the property table above. 382 */ 383 static final String CATALOG_RESOLVE = "javax.xml.catalog.resolve"; 384 385 //values for the prefer property 386 static final String PREFER_SYSTEM = "system"; 387 static final String PREFER_PUBLIC = "public"; 388 389 //values for the defer property 390 static final String DEFER_TRUE = "true"; 391 static final String DEFER_FALSE = "false"; 392 393 //values for the Resolve property 394 static final String RESOLVE_STRICT = "strict"; 395 static final String RESOLVE_CONTINUE = "continue"; 396 static final String RESOLVE_IGNORE = "ignore"; 397 398 /** 399 * A Feature type as defined in the 400 * <a href="CatalogFeatures.html#CatalogFeatures">Catalog Features table</a>. 401 */ 402 public static enum Feature { 403 /** 404 * The {@code javax.xml.catalog.files} property as described in 405 * item <a href="CatalogFeatures.html#FILES">FILES</a> of the 406 * Catalog Features table. 407 */ 408 FILES(CATALOG_FILES, null, true), 409 /** 410 * The {@code javax.xml.catalog.prefer} property as described in 411 * item <a href="CatalogFeatures.html#PREFER">PREFER</a> of the 412 * Catalog Features table. 413 */ 414 PREFER(CATALOG_PREFER, PREFER_PUBLIC, false), 415 /** 416 * The {@code javax.xml.catalog.defer} property as described in 417 * item <a href="CatalogFeatures.html#DEFER">DEFER</a> of the 418 * Catalog Features table. 419 */ 420 DEFER(CATALOG_DEFER, DEFER_TRUE, true), 421 /** 422 * The {@code javax.xml.catalog.resolve} property as described in 423 * item <a href="CatalogFeatures.html#RESOLVE">RESOLVE</a> of the 424 * Catalog Features table. 425 */ 426 RESOLVE(CATALOG_RESOLVE, RESOLVE_STRICT, true); 427 428 private final String name; 429 private final String defaultValue; 430 private String value; 431 private final boolean hasSystem; 432 433 /** 434 * Constructs a CatalogFeature instance. 435 * @param name the name of the feature 436 * @param value the value of the feature 437 * @param hasSystem a flag to indicate whether the feature is supported 438 * with a System property 439 */ 440 Feature(String name, String value, boolean hasSystem) { 441 this.name = name; 442 this.defaultValue = value; 443 this.hasSystem = hasSystem; 444 } 445 446 /** 447 * Checks whether the specified property is equal to the current property. 448 * @param propertyName the name of a property 449 * @return true if the specified property is the current property, false 450 * otherwise 451 */ 452 boolean equalsPropertyName(String propertyName) { 453 return name.equals(propertyName); 454 } 455 456 /** 457 * Returns the name of the corresponding System Property. 458 * 459 * @return the name of the System Property 460 */ 461 public String getPropertyName() { 462 return name; 463 } 464 465 /** 466 * Returns the default value of the property. 467 * @return the default value of the property 468 */ 469 public String defaultValue() { 470 return defaultValue; 471 } 472 473 /** 474 * Returns the value of the property. 475 * @return the value of the property 476 */ 477 String getValue() { 478 return value; 479 } 480 481 /** 482 * Checks whether System property is supported for the feature. 483 * @return true it is supported, false otherwise 484 */ 485 boolean hasSystemProperty() { 486 return hasSystem; 487 } 488 } 489 490 /** 491 * States of the settings of a property, in the order: default value, 492 * jaxp.properties file, jaxp system properties, and jaxp api properties 493 */ 494 static enum State { 495 /** represents the default state of a feature. */ 496 DEFAULT("default"), 497 /** indicates the value of the feature is read from jaxp.properties. */ 498 JAXPDOTPROPERTIES("jaxp.properties"), 499 /** indicates the value of the feature is read from its System property. */ 500 SYSTEMPROPERTY("system property"), 501 /** indicates the value of the feature is specified through the API. */ 502 APIPROPERTY("property"), 503 /** indicates the value of the feature is specified as a catalog attribute. */ 504 CATALOGATTRIBUTE("catalog attribute"); 505 506 final String literal; 507 508 State(String literal) { 509 this.literal = literal; 510 } 511 512 String literal() { 513 return literal; 514 } 515 } 516 517 /** 518 * Values of the properties 519 */ 520 private String[] values; 521 522 /** 523 * States of the settings for each property 524 */ 525 private State[] states; 526 527 /** 528 * Private class constructor 529 */ 530 private CatalogFeatures() { 531 } 532 533 /** 534 * Returns a CatalogFeatures instance with default settings. 535 * @return a default CatalogFeatures instance 536 */ 537 public static CatalogFeatures defaults() { 538 return CatalogFeatures.builder().build(); 539 } 540 541 /** 542 * Constructs a new CatalogFeatures instance with the builder. 543 * 544 * @param builder the builder to build the CatalogFeatures 545 */ 546 CatalogFeatures(Builder builder) { 547 init(); 548 setProperties(builder); 549 } 550 551 /** 552 * Returns the value of the specified feature. 553 * 554 * @param cf the type of the Catalog feature 555 * @return the value of the feature 556 */ 557 public String get(Feature cf) { 558 return values[cf.ordinal()]; 559 } 560 561 /** 562 * Initializes the supported properties 563 */ 564 private void init() { 565 values = new String[Feature.values().length]; 566 states = new State[Feature.values().length]; 567 for (Feature cf : Feature.values()) { 568 setProperty(cf, State.DEFAULT, cf.defaultValue()); 569 } 570 //read system properties or jaxp.properties 571 readSystemProperties(); 572 } 573 574 /** 575 * Sets properties by the Builder. 576 * @param builder the CatalogFeatures builder 577 */ 578 private void setProperties(Builder builder) { 579 builder.values.entrySet().stream().forEach((entry) -> { 580 setProperty(entry.getKey(), State.APIPROPERTY, entry.getValue()); 581 }); 582 } 583 /** 584 * Sets the value of a property, updates only if it shall override. 585 * 586 * @param index the index of the property 587 * @param state the state of the property 588 * @param value the value of the property 589 * @throws IllegalArgumentException if the value is invalid 590 */ 591 private void setProperty(Feature feature, State state, String value) { 592 int index = feature.ordinal(); 593 if (value != null && value.length() != 0) { 594 if (state != State.APIPROPERTY) { 595 Util.validateFeatureInput(feature, value); 596 } 597 if (states[index] == null || state.compareTo(states[index]) >= 0) { 598 values[index] = value; 599 states[index] = state; 600 } 601 } 602 } 603 604 /** 605 * Reads from system properties, or those in jaxp.properties 606 */ 607 private void readSystemProperties() { 608 for (Feature cf : Feature.values()) { 609 getSystemProperty(cf, cf.getPropertyName()); 610 } 611 } 612 613 /** 614 * Reads from system properties, or those in jaxp.properties 615 * 616 * @param cf the type of the property 617 * @param sysPropertyName the name of system property 618 */ 619 private boolean getSystemProperty(Feature cf, String sysPropertyName) { 620 if (cf.hasSystemProperty()) { 621 String value = SecuritySupport.getSystemProperty(sysPropertyName); 622 if (value != null && !value.isEmpty()) { 623 setProperty(cf, State.SYSTEMPROPERTY, value); 624 return true; 625 } 626 627 value = SecuritySupport.readJAXPProperty(sysPropertyName); 628 if (value != null && !value.isEmpty()) { 629 setProperty(cf, State.JAXPDOTPROPERTIES, value); 630 return true; 631 } 632 } 633 return false; 634 } 635 636 /** 637 * Returns an instance of the builder for creating the CatalogFeatures object. 638 * 639 * @return an instance of the builder 640 */ 641 public static Builder builder() { 642 return new CatalogFeatures.Builder(); 643 } 644 645 /** 646 * The Builder class for building the CatalogFeatures object. 647 */ 648 public static class Builder { 649 /** 650 * Values of the features supported by CatalogFeatures. 651 */ 652 Map<Feature, String> values = new HashMap<>(); 653 654 /** 655 * Instantiation of Builder is not allowed. 656 */ 657 private Builder() {} 658 659 /** 660 * Sets the value to a specified Feature. 661 * @param feature the Feature to be set 662 * @param value the value to be set for the Feature 663 * @return this Builder instance 664 * @throws IllegalArgumentException if the value is not valid for the 665 * Feature or has the wrong syntax for the {@code javax.xml.catalog.files} 666 * property 667 */ 668 public Builder with(Feature feature, String value) { 669 Util.validateFeatureInput(feature, value); 670 values.put(feature, value); 671 return this; 672 } 673 674 /** 675 * Returns a CatalogFeatures object built by this builder. 676 * 677 * @return an instance of CatalogFeatures 678 */ 679 public CatalogFeatures build() { 680 return new CatalogFeatures(this); 681 } 682 } 683 }