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.equals("")) {
623 setProperty(cf, State.SYSTEMPROPERTY, value);
624 return true;
625 }
626
627 value = SecuritySupport.readJAXPProperty(sysPropertyName);
628 if (value != null && !value.equals("")) {
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 }
684