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, nulltrue),
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