CatalogImpl.java revision 862:b34427de0b08
1178476Sjb/*
2178476Sjb * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3178476Sjb * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4178476Sjb *
5178476Sjb * This code is free software; you can redistribute it and/or modify it
6178476Sjb * under the terms of the GNU General Public License version 2 only, as
7178476Sjb * published by the Free Software Foundation.  Oracle designates this
8178476Sjb * particular file as subject to the "Classpath" exception as provided
9178476Sjb * by Oracle in the LICENSE file that accompanied this code.
10178476Sjb *
11178476Sjb * This code is distributed in the hope that it will be useful, but WITHOUT
12178476Sjb * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13178476Sjb * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14178476Sjb * version 2 for more details (a copy is included in the LICENSE file that
15178476Sjb * accompanied this code).
16178476Sjb *
17178476Sjb * You should have received a copy of the GNU General Public License version
18178476Sjb * 2 along with this work; if not, write to the Free Software Foundation,
19178476Sjb * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20178476Sjb *
21178476Sjb * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22178476Sjb * or visit www.oracle.com if you need additional information or have any
23178476Sjb * questions.
24178476Sjb */
25178476Sjbpackage javax.xml.catalog;
26178476Sjb
27178476Sjbimport com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
28178476Sjbimport java.io.File;
29178476Sjbimport java.io.IOException;
30178476Sjbimport java.net.MalformedURLException;
31178476Sjbimport java.net.URI;
32178476Sjbimport java.net.URISyntaxException;
33178476Sjbimport java.net.URL;
34178476Sjbimport java.util.ArrayList;
35178476Sjbimport java.util.Iterator;
36178476Sjbimport java.util.List;
37178476Sjbimport java.util.NoSuchElementException;
38178476Sjbimport java.util.Spliterator;
39178476Sjbimport java.util.Spliterators;
40178476Sjbimport java.util.stream.Stream;
41178476Sjbimport java.util.stream.StreamSupport;
42178476Sjbimport static javax.xml.catalog.BaseEntry.CatalogEntryType;
43178476Sjbimport static javax.xml.catalog.CatalogFeatures.DEFER_TRUE;
44178476Sjbimport javax.xml.catalog.CatalogFeatures.Feature;
45178476Sjbimport static javax.xml.catalog.CatalogMessages.formatMessage;
46178476Sjbimport javax.xml.parsers.ParserConfigurationException;
47178476Sjbimport javax.xml.parsers.SAXParser;
48178476Sjbimport javax.xml.parsers.SAXParserFactory;
49178476Sjbimport org.xml.sax.SAXException;
50178476Sjb
51178476Sjb/**
52178476Sjb * Implementation of the Catalog.
53178476Sjb *
54178476Sjb * @since 9
55178476Sjb */
56178476Sjbclass CatalogImpl extends GroupEntry implements Catalog {
57178476Sjb
58178476Sjb    //Catalog level, 0 means the top catalog
59178476Sjb    int level = 0;
60178476Sjb
61178476Sjb    //Value of the defer attribute to determine if alternative catalogs are read
62178476Sjb    boolean isDeferred = true;
63178476Sjb
64178476Sjb    //Value of the resolve attribute
65178476Sjb    ResolveType resolveType = ResolveType.STRICT;
66178476Sjb
67178476Sjb    //indicate whether the Catalog is empty
68178476Sjb    boolean isEmpty;
69178476Sjb
70178476Sjb    //Current parsed Catalog
71    int current = 0;
72
73    //System Id for this catalog
74    String systemId;
75
76    //The parent
77    CatalogImpl parent = null;
78
79    /*
80     A list of catalog entry files from the input, excluding the current catalog.
81     Paths in the List are normalized.
82     */
83    List<String> inputFiles;
84
85    //A list of catalogs specified using the nextCatalog element
86    List<NextCatalog> nextCatalogs;
87
88    //reuse the parser
89    SAXParser parser;
90
91    /**
92     * Construct a Catalog with specified path.
93     *
94     * @param file The path to a catalog file.
95     * @throws CatalogException If an error happens while parsing the specified
96     * catalog file.
97     */
98    public CatalogImpl(CatalogFeatures f, String... file) throws CatalogException {
99        this(null, f, file);
100    }
101
102    /**
103     * Construct a Catalog with specified path.
104     *
105     * @param parent The parent catalog
106     * @param file The path to a catalog file.
107     * @throws CatalogException If an error happens while parsing the specified
108     * catalog file.
109     */
110    public CatalogImpl(CatalogImpl parent, CatalogFeatures f, String... file) throws CatalogException {
111        super(CatalogEntryType.CATALOG);
112        if (f == null) {
113            throw new NullPointerException(
114                    formatMessage(CatalogMessages.ERR_NULL_ARGUMENT, new Object[]{"CatalogFeatures"}));
115        }
116
117        if (file.length > 0) {
118            CatalogMessages.reportNPEOnNull("The path to the catalog file", file[0]);
119        }
120
121        init(parent, f);
122
123        //Path of catalog files
124        String[] catalogFile = file;
125        if (level == 0 && file.length == 0) {
126            String files = features.get(Feature.FILES);
127            if (files != null) {
128                catalogFile = files.split(";[ ]*");
129            }
130        }
131
132        /*
133         In accordance with 8. Resource Failures of the Catalog spec, missing
134         Catalog entry files are to be ignored.
135         */
136        if ((catalogFile != null && catalogFile.length > 0)) {
137            int start = 0;
138            URI uri = null;
139            for (String temp : catalogFile) {
140                uri = getSystemId(temp);
141                start++;
142                if (verifyCatalogFile(uri)) {
143                    systemId = uri.toASCIIString();
144                    break;
145                }
146            }
147
148            //Save the rest of input files as alternative catalogs
149            if (level == 0 && catalogFile.length > start) {
150                inputFiles = new ArrayList<>();
151                for (int i = start; i < catalogFile.length; i++) {
152                    if (catalogFile[i] != null) {
153                        inputFiles.add(catalogFile[i]);
154                    }
155                }
156            }
157
158            if (systemId != null) {
159                parse(systemId);
160            }
161        }
162    }
163
164    private void init(CatalogImpl parent, CatalogFeatures f) {
165        this.parent = parent;
166        if (parent == null) {
167            level = 0;
168        } else {
169            level = parent.level + 1;
170        }
171        if (f == null) {
172            this.features = CatalogFeatures.defaults();
173        } else {
174            this.features = f;
175        }
176        setPrefer(features.get(Feature.PREFER));
177        setDeferred(features.get(Feature.DEFER));
178        setResolve(features.get(Feature.RESOLVE));
179    }
180
181    /**
182     * Resets the Catalog instance to its initial state.
183     */
184    @Override
185    public void reset() {
186        super.reset();
187        current = 0;
188        if (level == 0) {
189            catalogsSearched.clear();
190        }
191        entries.stream().filter((entry) -> (entry.type == CatalogEntryType.GROUP)).forEach((entry) -> {
192            ((GroupEntry) entry).reset();
193        });
194
195        if (parent != null) {
196            this.loadedCatalogs = parent.loadedCatalogs;
197            this.catalogsSearched = parent.catalogsSearched;
198        }
199    }
200
201    /**
202     * Returns whether this Catalog instance is the top (main) Catalog.
203     *
204     * @return true if the instance is the top Catalog, false otherwise
205     */
206    boolean isTop() {
207        return level == 0;
208    }
209
210    /**
211     * Gets the parent of this catalog.
212     *
213     * @returns The parent catalog
214     */
215    public Catalog getParent() {
216        return this.parent;
217    }
218
219    /**
220     * Sets the defer property. If the value is null or empty, or any String
221     * other than the defined, it will be assumed as the default value.
222     *
223     * @param value The value of the defer attribute
224     */
225    public final void setDeferred(String value) {
226        isDeferred = DEFER_TRUE.equals(value);
227    }
228
229    /**
230     * Queries the defer attribute
231     *
232     * @return true if the prefer attribute is set to system, false if not.
233     */
234    public boolean isDeferred() {
235        return isDeferred;
236    }
237
238    /**
239     * Sets the resolve property. If the value is null or empty, or any String
240     * other than the defined, it will be assumed as the default value.
241     *
242     * @param value The value of the resolve attribute
243     */
244    public final void setResolve(String value) {
245        resolveType = ResolveType.getType(value);
246    }
247
248    /**
249     * Gets the value of the resolve attribute
250     *
251     * @return The value of the resolve attribute
252     */
253    public final ResolveType getResolve() {
254        return resolveType;
255    }
256
257    /**
258     * Marks the Catalog as being searched already.
259     */
260    void markAsSearched() {
261        catalogsSearched.add(systemId);
262    }
263
264    /**
265     * Parses the catalog.
266     *
267     * @param systemId The systemId of the catalog
268     * @throws CatalogException if parsing the catalog failed
269     */
270    private void parse(String systemId) {
271        if (parser == null) {
272            parser = getParser();
273        }
274
275        try {
276            CatalogReader reader = new CatalogReader(this, parser);
277            parser.parse(systemId, reader);
278        } catch (SAXException | IOException ex) {
279            CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, ex);
280        }
281    }
282
283    /**
284     * Resolves the specified file path to an absolute systemId. If it is
285     * relative, it shall be resolved using the base or user.dir property if
286     * base is not specified.
287     *
288     * @param file The specified file path
289     * @return The systemId of the file
290     * @throws CatalogException if the specified file path can not be converted
291     * to a system id
292     */
293    private URI getSystemId(String file) {
294        URL filepath;
295        if (file != null && file.length() > 0) {
296            try {
297                File f = new File(file);
298                if (baseURI != null && !f.isAbsolute()) {
299                    filepath = new URL(baseURI, fixSlashes(file));
300                    return filepath.toURI();
301                } else {
302                    return resolveURI(file);
303                }
304            } catch (MalformedURLException | URISyntaxException e) {
305                CatalogMessages.reportRunTimeError(CatalogMessages.ERR_INVALID_PATH,
306                        new Object[]{file}, e);
307            }
308        }
309        return null;
310    }
311
312    /**
313     * Resolves the specified uri. If the uri is relative, makes it absolute by
314     * the user.dir directory.
315     *
316     * @param uri The specified URI.
317     * @return The resolved URI
318     */
319    private URI resolveURI(String uri) throws MalformedURLException {
320        if (uri == null) {
321            uri = "";
322        }
323
324        URI temp = toURI(uri);
325        String str = temp.toASCIIString();
326        String base = str.substring(0, str.lastIndexOf('/') + 1);
327        baseURI = new URL(str);
328
329        return temp;
330    }
331
332    /**
333     * Converts an URI string or file path to URI.
334     *
335     * @param uri an URI string or file path
336     * @return an URI
337     */
338    private URI toURI(String uri) {
339        URI temp = null;
340        try {
341            URL url = new URL(uri);
342            temp = url.toURI();
343        } catch (MalformedURLException | URISyntaxException mue) {
344            File file = new File(uri);
345            temp = file.toURI();
346        }
347        return temp;
348    }
349
350    /**
351     * Returns a SAXParser instance
352     * @return a SAXParser instance
353     * @throws CatalogException if constructing a SAXParser failed
354     */
355    private SAXParser getParser() {
356        SAXParser p = null;
357        try {
358            SAXParserFactory spf = new SAXParserFactoryImpl();
359            spf.setNamespaceAware(true);
360            spf.setValidating(false);
361            spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
362            p = spf.newSAXParser();
363        } catch (ParserConfigurationException | SAXException e) {
364            CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, e);
365        }
366        return p;
367    }
368
369    /**
370     * Indicate that the catalog is empty
371     *
372     * @return True if the catalog is empty; False otherwise.
373     */
374    public boolean isEmpty() {
375        return isEmpty;
376    }
377
378    @Override
379    public Stream<Catalog> catalogs() {
380        Iterator<Catalog> iter = new Iterator<Catalog>() {
381            Catalog nextCatalog = null;
382
383            //Current index of the input files
384            int inputFilesIndex = 0;
385
386            //Next catalog
387            int nextCatalogIndex = 0;
388
389            @Override
390            public boolean hasNext() {
391                if (nextCatalog != null) {
392                    return true;
393                } else {
394                    nextCatalog = nextCatalog();
395                    return (nextCatalog != null);
396                }
397            }
398
399            @Override
400            public Catalog next() {
401                if (nextCatalog != null || hasNext()) {
402                    Catalog catalog = nextCatalog;
403                    nextCatalog = null;
404                    return catalog;
405                } else {
406                    throw new NoSuchElementException();
407                }
408            }
409
410            /**
411             * Returns the next alternative catalog.
412             *
413             * @return the next catalog if any
414             */
415            private Catalog nextCatalog() {
416                Catalog c = null;
417
418                //Check those specified in nextCatalogs
419                if (nextCatalogs != null) {
420                    while (c == null && nextCatalogIndex < nextCatalogs.size()) {
421                        c = getCatalog(nextCatalogs.get(nextCatalogIndex++).getCatalogURI());
422                    }
423                }
424
425                //Check the input list
426                if (c == null && inputFiles != null) {
427                    while (c == null && inputFilesIndex < inputFiles.size()) {
428                        c = getCatalog(getSystemId(inputFiles.get(inputFilesIndex++)));
429                    }
430                }
431
432                return c;
433            }
434        };
435
436        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
437                iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
438    }
439
440    /**
441     * Adds the catalog to the nextCatalog list
442     *
443     * @param catalog a catalog specified in a nextCatalog entry
444     */
445    void addNextCatalog(NextCatalog catalog) {
446        if (catalog == null) {
447            return;
448        }
449
450        if (nextCatalogs == null) {
451            nextCatalogs = new ArrayList<>();
452        }
453
454        nextCatalogs.add(catalog);
455    }
456
457    /**
458     * Loads all alternative catalogs.
459     */
460    void loadNextCatalogs() {
461        //loads catalogs specified in nextCatalogs
462        if (nextCatalogs != null) {
463            for (NextCatalog next : nextCatalogs) {
464                getCatalog(next.getCatalogURI());
465            }
466        }
467
468        //loads catalogs from the input list
469        if (inputFiles != null) {
470            for (String file : inputFiles) {
471                getCatalog(getSystemId(file));
472            }
473        }
474    }
475
476    /**
477     * Returns a Catalog object by the specified path.
478     *
479     * @param path the path to a catalog
480     * @return a Catalog object
481     */
482    Catalog getCatalog(URI uri) {
483        if (uri == null) {
484            return null;
485        }
486
487        Catalog c = null;
488        String path = uri.toASCIIString();
489
490        if (verifyCatalogFile(uri)) {
491            c = getLoadedCatalog(path);
492            if (c == null) {
493                c = new CatalogImpl(this, features, path);
494                saveLoadedCatalog(path, c);
495            }
496        }
497        return c;
498    }
499
500    /**
501     * Saves a loaded Catalog.
502     *
503     * @param catalogId the catalogId associated with the Catalog object
504     * @param c the Catalog to be saved
505     */
506    void saveLoadedCatalog(String catalogId, Catalog c) {
507        loadedCatalogs.put(catalogId, c);
508    }
509
510    /**
511     * Returns a count of all loaded catalogs, including delegate catalogs.
512     *
513     * @return a count of all loaded catalogs
514     */
515    int loadedCatalogCount() {
516        return loadedCatalogs.size() + delegateCatalogs.size();
517    }
518}
519