1/*
2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package com.sun.org.apache.xml.internal.resolver;
22
23import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
24import com.sun.org.apache.xerces.internal.utils.SecuritySupport;
25import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
26import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;
27import com.sun.org.apache.xml.internal.resolver.readers.CatalogReader;
28import com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader;
29import com.sun.org.apache.xml.internal.resolver.readers.SAXCatalogReader;
30import com.sun.org.apache.xml.internal.resolver.readers.TR9401CatalogReader;
31import java.io.DataInputStream;
32import java.io.FileNotFoundException;
33import java.io.IOException;
34import java.io.InputStream;
35import java.io.UnsupportedEncodingException;
36import java.net.MalformedURLException;
37import java.net.URL;
38import java.util.Enumeration;
39import java.util.HashMap;
40import java.util.Locale;
41import java.util.Map;
42import java.util.Vector;
43import javax.xml.parsers.SAXParserFactory;
44
45/**
46 * Represents OASIS Open Catalog files.
47 *
48 * <p>This class implements the semantics of OASIS Open Catalog files
49 * (defined by
50 * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical
51 * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>).</p>
52 *
53 * <p>The primary purpose of the Catalog is to associate resources in the
54 * document with local system identifiers. Some entities
55 * (document types, XML entities, and notations) have names and all of them
56 * can have either public or system identifiers or both. (In XML, only a
57 * notation can have a public identifier without a system identifier, but
58 * the methods implemented in this class obey the Catalog semantics
59 * from the SGML
60 * days when system identifiers were optional.)</p>
61 *
62 * <p>The system identifiers returned by the resolution methods in this
63 * class are valid, i.e. usable by, and in fact constructed by, the
64 * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
65 * somewhat non-standard ways and the system identifiers returned may
66 * not be directly usable in a browser or filesystem context.
67 *
68 * <p>This class recognizes all of the Catalog entries defined in
69 * TR9401:1997:</p>
70 *
71 * <ul>
72 * <li><b>BASE</b>
73 * changes the base URI for resolving relative system identifiers. The
74 * initial base URI is the URI of the location of the catalog (which is,
75 * in turn, relative to the location of the current working directory
76 * at startup, as returned by the <tt>user.dir</tt> system property).</li>
77 * <li><b>CATALOG</b>
78 * processes other catalog files. An included catalog occurs logically
79 * at the end of the including catalog.</li>
80 * <li><b>DELEGATE_PUBLIC</b>
81 * specifies alternate catalogs for some public identifiers. The delegated
82 * catalogs are not loaded until they are needed, but they are cached
83 * once loaded.</li>
84 * <li><b>DELEGATE_SYSTEM</b>
85 * specifies alternate catalogs for some system identifiers. The delegated
86 * catalogs are not loaded until they are needed, but they are cached
87 * once loaded.</li>
88 * <li><b>DELEGATE_URI</b>
89 * specifies alternate catalogs for some URIs. The delegated
90 * catalogs are not loaded until they are needed, but they are cached
91 * once loaded.</li>
92 * <li><b>REWRITE_SYSTEM</b>
93 * specifies alternate prefix for a system identifier.</li>
94 * <li><b>REWRITE_URI</b>
95 * specifies alternate prefix for a URI.</li>
96 * <li><b>SYSTEM_SUFFIX</b>
97 * maps any system identifier that ends with a particular suffix to another
98 * system identifier.</li>
99 * <li><b>URI_SUFFIX</b>
100 * maps any URI that ends with a particular suffix to another URI.</li>
101 * <li><b>DOCTYPE</b>
102 * associates the names of root elements with URIs. (In other words, an XML
103 * processor might infer the doctype of an XML document that does not include
104 * a doctype declaration by looking for the DOCTYPE entry in the
105 * catalog which matches the name of the root element of the document.)</li>
106 * <li><b>DOCUMENT</b>
107 * provides a default document.</li>
108 * <li><b>DTDDECL</b>
109 * recognized and silently ignored. Not relevant for XML.</li>
110 * <li><b>ENTITY</b>
111 * associates entity names with URIs.</li>
112 * <li><b>LINKTYPE</b>
113 * recognized and silently ignored. Not relevant for XML.</li>
114 * <li><b>NOTATION</b>
115 * associates notation names with URIs.</li>
116 * <li><b>OVERRIDE</b>
117 * changes the override behavior. Initial behavior is set by the
118 * system property <tt>xml.catalog.override</tt>. The default initial
119 * behavior is 'YES', that is, entries in the catalog override
120 * system identifiers specified in the document.</li>
121 * <li><b>PUBLIC</b>
122 * maps a public identifier to a system identifier.</li>
123 * <li><b>SGMLDECL</b>
124 * recognized and silently ignored. Not relevant for XML.</li>
125 * <li><b>SYSTEM</b>
126 * maps a system identifier to another system identifier.</li>
127 * <li><b>URI</b>
128 * maps a URI to another URI.</li>
129 * </ul>
130 *
131 * <p>Note that BASE entries are treated as described by RFC2396. In
132 * particular, this has the counter-intuitive property that after a BASE
133 * entry identifing "http://example.com/a/b/c" as the base URI,
134 * the relative URI "foo" is resolved to the absolute URI
135 * "http://example.com/a/b/foo". You must provide the trailing slash if
136 * you do not want the final component of the path to be discarded as a
137 * filename would in a URI for a resource: "http://example.com/a/b/c/".
138 * </p>
139 *
140 * <p>Note that subordinate catalogs (all catalogs except the first,
141 * including CATALOG and DELEGATE* catalogs) are only loaded if and when
142 * they are required.</p>
143 *
144 * <p>This class relies on classes which implement the CatalogReader
145 * interface to actually load catalog files. This allows the catalog
146 * semantics to be implemented for TR9401 text-based catalogs, XML
147 * catalogs, or any number of other storage formats.</p>
148 *
149 * <p>Additional catalogs may also be loaded with the
150 * {@link #parseCatalog} method.</p>
151 *
152 * <p><b>Change Log:</b></p>
153 * <dl>
154 * <dt>2.0</dt>
155 * <dd><p>Rewrite to use CatalogReaders.</p></dd>
156 * <dt>1.1</dt>
157 * <dd><p>Allow quoted components in <tt>xml.catalog.files</tt>
158 * so that URLs containing colons can be used on Unix.
159 * The string passed to <tt>xml.catalog.files</tt> can now have the form:</p>
160 * <pre>
161 * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars'
162 * </pre>
163 * <p>(Where ":" is the separater character in this example.)</p>
164 * <p>If an unquoted path contains an embedded double or single quote
165 * character, no special processig is performed on that character. No
166 * path can contain separater characters, double, and single quotes
167 * simultaneously.</p>
168 * <p>Fix bug in calculation of BASE entries: if
169 * a catalog contains multiple BASE entries, each is relative to the preceding
170 * base, not the default base URI of the catalog.</p>
171 * </dd>
172 * <dt>1.0.1</dt>
173 * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs.
174 * This bug caused an infinite loop where parsing would alternately process
175 * two catalogs indefinitely.</p>
176 * </dd>
177 * </dl>
178 *
179 * @see CatalogReader
180 * @see CatalogEntry
181 * @deprecated The JDK internal Catalog API in package
182 * {@code com.sun.org.apache.xml.internal.resolver}
183 * is encapsulated in JDK 9. The entire implementation under the package is now
184 * deprecated and subject to removal in a future release. Users of the API
185 * should migrate to the {@linkplain javax.xml.catalog new public API}.
186 * <p>
187 * The new Catalog API is supported throughout the JDK XML Processors, which allows
188 * the use of Catalog by simply setting a path to a Catalog file as a property.
189 *
190 * @author Norman Walsh
191 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
192 *
193 * @version 1.0
194 *
195 * <p>Derived from public domain code originally published by Arbortext,
196 * Inc.</p>
197 */
198@Deprecated(since="9", forRemoval=true)
199public class Catalog {
200  /** The BASE Catalog Entry type. */
201  public static final int BASE     = CatalogEntry.addEntryType("BASE", 1);
202
203  /** The CATALOG Catalog Entry type. */
204  public static final int CATALOG  = CatalogEntry.addEntryType("CATALOG", 1);
205
206  /** The DOCUMENT Catalog Entry type. */
207  public static final int DOCUMENT = CatalogEntry.addEntryType("DOCUMENT", 1);
208
209  /** The OVERRIDE Catalog Entry type. */
210  public static final int OVERRIDE = CatalogEntry.addEntryType("OVERRIDE", 1);
211
212  /** The SGMLDECL Catalog Entry type. */
213  public static final int SGMLDECL = CatalogEntry.addEntryType("SGMLDECL", 1);
214
215  /** The DELEGATE_PUBLIC Catalog Entry type. */
216  public static final int DELEGATE_PUBLIC = CatalogEntry.addEntryType("DELEGATE_PUBLIC", 2);
217
218  /** The DELEGATE_SYSTEM Catalog Entry type. */
219  public static final int DELEGATE_SYSTEM = CatalogEntry.addEntryType("DELEGATE_SYSTEM", 2);
220
221  /** The DELEGATE_URI Catalog Entry type. */
222  public static final int DELEGATE_URI = CatalogEntry.addEntryType("DELEGATE_URI", 2);
223
224  /** The DOCTYPE Catalog Entry type. */
225  public static final int DOCTYPE  = CatalogEntry.addEntryType("DOCTYPE", 2);
226
227  /** The DTDDECL Catalog Entry type. */
228  public static final int DTDDECL  = CatalogEntry.addEntryType("DTDDECL", 2);
229
230  /** The ENTITY Catalog Entry type. */
231  public static final int ENTITY   = CatalogEntry.addEntryType("ENTITY", 2);
232
233  /** The LINKTYPE Catalog Entry type. */
234  public static final int LINKTYPE = CatalogEntry.addEntryType("LINKTYPE", 2);
235
236  /** The NOTATION Catalog Entry type. */
237  public static final int NOTATION = CatalogEntry.addEntryType("NOTATION", 2);
238
239  /** The PUBLIC Catalog Entry type. */
240  public static final int PUBLIC   = CatalogEntry.addEntryType("PUBLIC", 2);
241
242  /** The SYSTEM Catalog Entry type. */
243  public static final int SYSTEM   = CatalogEntry.addEntryType("SYSTEM", 2);
244
245  /** The URI Catalog Entry type. */
246  public static final int URI      = CatalogEntry.addEntryType("URI", 2);
247
248  /** The REWRITE_SYSTEM Catalog Entry type. */
249  public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType("REWRITE_SYSTEM", 2);
250
251  /** The REWRITE_URI Catalog Entry type. */
252  public static final int REWRITE_URI = CatalogEntry.addEntryType("REWRITE_URI", 2);
253  /** The SYSTEM_SUFFIX Catalog Entry type. */
254  public static final int SYSTEM_SUFFIX = CatalogEntry.addEntryType("SYSTEM_SUFFIX", 2);
255  /** The URI_SUFFIX Catalog Entry type. */
256  public static final int URI_SUFFIX = CatalogEntry.addEntryType("URI_SUFFIX", 2);
257
258  /**
259   * The base URI for relative system identifiers in the catalog.
260   * This may be changed by BASE entries in the catalog.
261   */
262  protected URL base;
263
264  /** The base URI of the Catalog file currently being parsed. */
265  protected URL catalogCwd;
266
267  /** The catalog entries currently known to the system. */
268  protected Vector catalogEntries = new Vector();
269
270  /** The default initial override setting. */
271  protected boolean default_override = true;
272
273  /** The catalog manager in use for this instance. */
274  protected CatalogManager catalogManager = CatalogManager.getStaticManager();
275
276  /**
277   * A vector of catalog files to be loaded.
278   *
279   * <p>This list is initially established by
280   * <code>loadSystemCatalogs</code> when
281   * it parses the system catalog list, but CATALOG entries may
282   * contribute to it during the course of parsing.</p>
283   *
284   * @see #loadSystemCatalogs
285   * @see #localCatalogFiles
286   */
287  protected Vector catalogFiles = new Vector();
288
289  /**
290   * A vector of catalog files constructed during processing of
291   * CATALOG entries in the current catalog.
292   *
293   * <p>This two-level system is actually necessary to correctly implement
294   * the semantics of the CATALOG entry. If one catalog file includes
295   * another with a CATALOG entry, the included catalog logically
296   * occurs <i>at the end</i> of the including catalog, and after any
297   * preceding CATALOG entries. In other words, the CATALOG entry
298   * cannot insert anything into the middle of a catalog file.</p>
299   *
300   * <p>When processing reaches the end of each catalog files, any
301   * elements on this vector are added to the front of the
302   * <code>catalogFiles</code> vector.</p>
303   *
304   * @see #catalogFiles
305   */
306  protected Vector localCatalogFiles = new Vector();
307
308  /**
309   * A vector of Catalogs.
310   *
311   * <p>The semantics of Catalog resolution are such that each
312   * catalog is effectively a list of Catalogs (in other words,
313   * a recursive list of Catalog instances).</p>
314   *
315   * <p>Catalogs that are processed as the result of CATALOG or
316   * DELEGATE* entries are subordinate to the catalog that contained
317   * them, but they may in turn have subordinate catalogs.</p>
318   *
319   * <p>Catalogs are only loaded when they are needed, so this vector
320   * initially contains a list of Catalog filenames (URLs). If, during
321   * processing, one of these catalogs has to be loaded, the resulting
322   * Catalog object is placed in the vector, effectively caching it
323   * for the next query.</p>
324   */
325  protected Vector catalogs = new Vector();
326
327  /**
328   * A vector of DELEGATE* Catalog entries constructed during
329   * processing of the Catalog.
330   *
331   * <p>This two-level system has two purposes; first, it allows
332   * us to sort the DELEGATE* entries by the length of the partial
333   * public identifier so that a linear search encounters them in
334   * the correct order and second, it puts them all at the end of
335   * the Catalog.</p>
336   *
337   * <p>When processing reaches the end of each catalog file, any
338   * elements on this vector are added to the end of the
339   * <code>catalogEntries</code> vector. This assures that matching
340   * PUBLIC keywords are encountered before DELEGATE* entries.</p>
341   */
342  protected Vector localDelegate = new Vector();
343
344  /**
345   * A hash of CatalogReaders.
346   *
347   * <p>This hash maps MIME types to elements in the readerArr
348   * vector. This allows the Catalog to quickly locate the reader
349   * for a particular MIME type.</p>
350   */
351  protected Map<String, Integer> readerMap = new HashMap<>();
352
353  /**
354   * A vector of CatalogReaders.
355   *
356   * <p>This vector contains all of the readers in the order that they
357   * were added. In the event that a catalog is read from a file, where
358   * the MIME type is unknown, each reader is attempted in turn until
359   * one succeeds.</p>
360   */
361  protected Vector readerArr = new Vector();
362
363  /**
364   * Constructs an empty Catalog.
365   *
366   * <p>The constructor interrogates the relevant system properties
367   * using the default (static) CatalogManager
368   * and initializes the catalog data structures.</p>
369   */
370  public Catalog() {
371    // nop;
372  }
373
374  /**
375   * Constructs an empty Catalog with a specific CatalogManager.
376   *
377   * <p>The constructor interrogates the relevant system properties
378   * using the specified Catalog Manager
379   * and initializes the catalog data structures.</p>
380   */
381  public Catalog(CatalogManager manager) {
382    catalogManager = manager;
383  }
384
385  /**
386   * Return the CatalogManager used by this catalog.
387   *
388   */
389  public CatalogManager getCatalogManager() {
390    return catalogManager;
391  }
392
393  /**
394   * Establish the CatalogManager used by this catalog.
395   *
396   */
397  public void setCatalogManager(CatalogManager manager) {
398    catalogManager = manager;
399  }
400
401  /**
402   * Setup readers.
403   */
404  public void setupReaders() {
405    SAXParserFactory spf = catalogManager.useServicesMechanism() ?
406                    SAXParserFactory.newInstance() : new SAXParserFactoryImpl();
407    spf.setNamespaceAware(true);
408    spf.setValidating(false);
409
410    SAXCatalogReader saxReader = new SAXCatalogReader(spf);
411
412    saxReader.setCatalogParser(null, "XMLCatalog",
413                               "com.sun.org.apache.xml.internal.resolver.readers.XCatalogReader");
414
415    saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName,
416                               "catalog",
417                               "com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader");
418
419    addReader("application/xml", saxReader);
420
421    TR9401CatalogReader textReader = new TR9401CatalogReader();
422    addReader("text/plain", textReader);
423  }
424
425  /**
426   * Add a new CatalogReader to the Catalog.
427   *
428   * <p>This method allows you to add a new CatalogReader to the
429   * catalog. The reader will be associated with the specified mimeType.
430   * You can only have one reader per mimeType.</p>
431   *
432   * <p>In the absence of a mimeType (e.g., when reading a catalog
433   * directly from a file on the local system), the readers are attempted
434   * in the order that you add them to the Catalog.</p>
435   *
436   * <p>Note that subordinate catalogs (created by CATALOG or
437   * DELEGATE* entries) get a copy of the set of readers present in
438   * the primary catalog when they are created. Readers added subsequently
439   * will not be available. For this reason, it is best to add all
440   * of the readers before the first call to parse a catalog.</p>
441   *
442   * @param mimeType The MIME type associated with this reader.
443   * @param reader The CatalogReader to use.
444   */
445  public void addReader(String mimeType, CatalogReader reader) {
446    if (readerMap.containsKey(mimeType)) {
447      Integer pos = readerMap.get(mimeType);
448      readerArr.set(pos, reader);
449    } else {
450      readerArr.add(reader);
451      Integer pos = readerArr.size()-1;
452      readerMap.put(mimeType, pos);
453    }
454  }
455
456  /**
457   * Copies the reader list from the current Catalog to a new Catalog.
458   *
459   * <p>This method is used internally when constructing a new catalog.
460   * It copies the current reader associations over to the new catalog.
461   * </p>
462   *
463   * @param newCatalog The new Catalog.
464   */
465  protected void copyReaders(Catalog newCatalog) {
466    // Have to copy the readers in the right order...convert hash to arr
467    Vector mapArr = new Vector(readerMap.size());
468
469    // Pad the mapArr out to the right length
470    for (int count = 0; count < readerMap.size(); count++) {
471      mapArr.add(null);
472    }
473
474    for (Map.Entry<String, Integer> entry : readerMap.entrySet()) {
475        mapArr.set(entry.getValue().intValue(), entry.getKey());
476    }
477
478    for (int count = 0; count < mapArr.size(); count++) {
479      String mimeType = (String) mapArr.get(count);
480      Integer pos = readerMap.get(mimeType);
481      newCatalog.addReader(mimeType,
482                           (CatalogReader)
483                           readerArr.get(pos.intValue()));
484    }
485  }
486
487  /**
488   * Create a new Catalog object.
489   *
490   * <p>This method constructs a new instance of the running Catalog
491   * class (which might be a subtype of com.sun.org.apache.xml.internal.resolver.Catalog).
492   * All new catalogs are managed by the same CatalogManager.
493   * </p>
494   *
495   * <p>N.B. All Catalog subtypes should call newCatalog() to construct
496   * a new Catalog. Do not simply use "new Subclass()" since that will
497   * confuse future subclasses.</p>
498   */
499  protected Catalog newCatalog() {
500    String catalogClass = this.getClass().getName();
501
502    try {
503      Catalog c = (Catalog) (Class.forName(catalogClass).newInstance());
504      c.setCatalogManager(catalogManager);
505      copyReaders(c);
506      return c;
507    } catch (ClassNotFoundException cnfe) {
508      catalogManager.debug.message(1, "Class Not Found Exception: " + catalogClass);
509    } catch (IllegalAccessException iae) {
510      catalogManager.debug.message(1, "Illegal Access Exception: " + catalogClass);
511    } catch (InstantiationException ie) {
512      catalogManager.debug.message(1, "Instantiation Exception: " + catalogClass);
513    } catch (ClassCastException cce) {
514      catalogManager.debug.message(1, "Class Cast Exception: " + catalogClass);
515    } catch (Exception e) {
516      catalogManager.debug.message(1, "Other Exception: " + catalogClass);
517    }
518
519    Catalog c = new Catalog();
520    c.setCatalogManager(catalogManager);
521    copyReaders(c);
522    return c;
523  }
524
525  /**
526   * Returns the current base URI.
527   */
528  public String getCurrentBase() {
529    return base.toString();
530  }
531
532  /**
533   * Returns the default override setting associated with this
534   * catalog.
535   *
536   * <p>All catalog files loaded by this catalog will have the
537   * initial override setting specified by this default.</p>
538   */
539  public String getDefaultOverride() {
540    if (default_override) {
541      return "yes";
542    } else {
543      return "no";
544    }
545  }
546
547  /**
548   * Load the system catalog files.
549   *
550   * <p>The method adds all of the
551   * catalogs specified in the <tt>xml.catalog.files</tt> property
552   * to the Catalog list.</p>
553   *
554   * @throws MalformedURLException  One of the system catalogs is
555   * identified with a filename that is not a valid URL.
556   * @throws IOException One of the system catalogs cannot be read.
557   */
558  public void loadSystemCatalogs()
559    throws MalformedURLException, IOException {
560
561    Vector catalogs = catalogManager.getCatalogFiles();
562    if (catalogs != null) {
563      for (int count = 0; count < catalogs.size(); count++) {
564        catalogFiles.addElement(catalogs.elementAt(count));
565      }
566    }
567
568    if (catalogFiles.size() > 0) {
569      // This is a little odd. The parseCatalog() method expects
570      // a filename, but it adds that name to the end of the
571      // catalogFiles vector, and then processes that vector.
572      // This allows the system to handle CATALOG entries
573      // correctly.
574      //
575      // In this init case, we take the last element off the
576      // catalogFiles vector and pass it to parseCatalog. This
577      // will "do the right thing" in the init case, and allow
578      // parseCatalog() to do the right thing in the non-init
579      // case. Honest.
580      //
581      String catfile = (String) catalogFiles.lastElement();
582      catalogFiles.removeElement(catfile);
583      parseCatalog(catfile);
584    }
585  }
586
587  /**
588   * Parse a catalog file, augmenting internal data structures.
589   *
590   * @param fileName The filename of the catalog file to process
591   *
592   * @throws MalformedURLException The fileName cannot be turned into
593   * a valid URL.
594   * @throws IOException Error reading catalog file.
595   */
596  public synchronized void parseCatalog(String fileName)
597    throws MalformedURLException, IOException {
598
599    default_override = catalogManager.getPreferPublic();
600    catalogManager.debug.message(4, "Parse catalog: " + fileName);
601
602    // Put the file into the list of catalogs to process...
603    // In all cases except the case when initCatalog() is the
604    // caller, this will be the only catalog initially in the list...
605    catalogFiles.addElement(fileName);
606
607    // Now process all the pending catalogs...
608    parsePendingCatalogs();
609  }
610
611  /**
612   * Parse a catalog file, augmenting internal data structures.
613   *
614   * <p>Catalogs retrieved over the net may have an associated MIME type.
615   * The MIME type can be used to select an appropriate reader.</p>
616   *
617   * @param mimeType The MIME type of the catalog file.
618   * @param is The InputStream from which the catalog should be read
619   *
620   * @throws CatalogException Failed to load catalog
621   * mimeType.
622   * @throws IOException Error reading catalog file.
623   */
624  public synchronized void parseCatalog(String mimeType, InputStream is)
625    throws IOException, CatalogException {
626
627    default_override = catalogManager.getPreferPublic();
628    catalogManager.debug.message(4, "Parse " + mimeType + " catalog on input stream");
629
630    CatalogReader reader = null;
631
632    if (readerMap.containsKey(mimeType)) {
633      int arrayPos = ((Integer) readerMap.get(mimeType)).intValue();
634      reader = (CatalogReader) readerArr.get(arrayPos);
635    }
636
637    if (reader == null) {
638      String msg = "No CatalogReader for MIME type: " + mimeType;
639      catalogManager.debug.message(2, msg);
640      throw new CatalogException(CatalogException.UNPARSEABLE, msg);
641    }
642
643    reader.readCatalog(this, is);
644
645    // Now process all the pending catalogs...
646    parsePendingCatalogs();
647  }
648
649  /**
650   * Parse a catalog document, augmenting internal data structures.
651   *
652   * <p>This method supports catalog files stored in jar files: e.g.,
653   * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI
654   * doesn't survive transmogrification through the URI processing that
655   * the parseCatalog(String) performs and passing it as an input stream
656   * doesn't set the base URI appropriately.</p>
657   *
658   * <p>Written by Stefan Wachter (2002-09-26)</p>
659   *
660   * @param aUrl The URL of the catalog document to process
661   *
662   * @throws IOException Error reading catalog file.
663   */
664  public synchronized void parseCatalog(URL aUrl) throws IOException {
665    catalogCwd = aUrl;
666    base = aUrl;
667
668    default_override = catalogManager.getPreferPublic();
669    catalogManager.debug.message(4, "Parse catalog: " + aUrl.toString());
670
671    DataInputStream inStream = null;
672    boolean parsed = false;
673
674    for (int count = 0; !parsed && count < readerArr.size(); count++) {
675      CatalogReader reader = (CatalogReader) readerArr.get(count);
676
677      try {
678        inStream = new DataInputStream(aUrl.openStream());
679      } catch (FileNotFoundException fnfe) {
680        // No catalog; give up!
681        break;
682      }
683
684      try {
685        reader.readCatalog(this, inStream);
686        parsed=true;
687      } catch (CatalogException ce) {
688        if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
689          // give up!
690          break;
691        } else {
692          // try again!
693        }
694      }
695
696      try {
697        inStream.close();
698      } catch (IOException e) {
699        //nop
700      }
701    }
702
703    if (parsed) parsePendingCatalogs();
704  }
705
706  /**
707   * Parse all of the pending catalogs.
708   *
709   * <p>Catalogs may refer to other catalogs, this method parses
710   * all of the currently pending catalog files.</p>
711   */
712  protected synchronized void parsePendingCatalogs()
713    throws MalformedURLException, IOException {
714
715    if (!localCatalogFiles.isEmpty()) {
716      // Move all the localCatalogFiles into the front of
717      // the catalogFiles queue
718      Vector newQueue = new Vector();
719      Enumeration q = localCatalogFiles.elements();
720      while (q.hasMoreElements()) {
721        newQueue.addElement(q.nextElement());
722      }
723
724      // Put the rest of the catalogs on the end of the new list
725      for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
726        String catfile = (String) catalogFiles.elementAt(curCat);
727        newQueue.addElement(catfile);
728      }
729
730      catalogFiles = newQueue;
731      localCatalogFiles.clear();
732    }
733
734    // Suppose there are no catalog files to process, but the
735    // single catalog already parsed included some delegate
736    // entries? Make sure they don't get lost.
737    if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) {
738      Enumeration e = localDelegate.elements();
739      while (e.hasMoreElements()) {
740        catalogEntries.addElement(e.nextElement());
741      }
742      localDelegate.clear();
743    }
744
745    // Now process all the files on the catalogFiles vector. This
746    // vector can grow during processing if CATALOG entries are
747    // encountered in the catalog
748    while (!catalogFiles.isEmpty()) {
749      String catfile = (String) catalogFiles.elementAt(0);
750      try {
751        catalogFiles.remove(0);
752      } catch (ArrayIndexOutOfBoundsException e) {
753        // can't happen
754      }
755
756      if (catalogEntries.size() == 0 && catalogs.size() == 0) {
757        // We haven't parsed any catalogs yet, let this
758        // catalog be the first...
759        try {
760          parseCatalogFile(catfile);
761        } catch (CatalogException ce) {
762          System.out.println("FIXME: " + ce.toString());
763        }
764      } else {
765        // This is a subordinate catalog. We save its name,
766        // but don't bother to load it unless it's necessary.
767        catalogs.addElement(catfile);
768      }
769
770      if (!localCatalogFiles.isEmpty()) {
771        // Move all the localCatalogFiles into the front of
772        // the catalogFiles queue
773        Vector newQueue = new Vector();
774        Enumeration q = localCatalogFiles.elements();
775        while (q.hasMoreElements()) {
776          newQueue.addElement(q.nextElement());
777        }
778
779        // Put the rest of the catalogs on the end of the new list
780        for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
781          catfile = (String) catalogFiles.elementAt(curCat);
782          newQueue.addElement(catfile);
783        }
784
785        catalogFiles = newQueue;
786        localCatalogFiles.clear();
787      }
788
789      if (!localDelegate.isEmpty()) {
790        Enumeration e = localDelegate.elements();
791        while (e.hasMoreElements()) {
792          catalogEntries.addElement(e.nextElement());
793        }
794        localDelegate.clear();
795      }
796    }
797
798    // We've parsed them all, reinit the vector...
799    catalogFiles.clear();
800  }
801
802  /**
803   * Parse a single catalog file, augmenting internal data structures.
804   *
805   * @param fileName The filename of the catalog file to process
806   *
807   * @throws MalformedURLException The fileName cannot be turned into
808   * a valid URL.
809   * @throws IOException Error reading catalog file.
810   */
811  protected synchronized void parseCatalogFile(String fileName)
812    throws MalformedURLException, IOException, CatalogException {
813
814    CatalogEntry entry;
815
816    // The base-base is the cwd. If the catalog file is specified
817    // with a relative path, this assures that it gets resolved
818    // properly...
819    try {
820      // tack on a basename because URLs point to files not dirs
821      catalogCwd = FileURL.makeURL("basename");
822    } catch (MalformedURLException e) {
823      catalogManager.debug.message(1, "Malformed URL on cwd", "user.dir");
824      catalogCwd = null;
825    }
826
827    // The initial base URI is the location of the catalog file
828    try {
829      base = new URL(catalogCwd, fixSlashes(fileName));
830    } catch (MalformedURLException e) {
831      try {
832        base = new URL("file:" + fixSlashes(fileName));
833      } catch (MalformedURLException e2) {
834        catalogManager.debug.message(1, "Malformed URL on catalog filename",
835                      fixSlashes(fileName));
836        base = null;
837      }
838    }
839
840    catalogManager.debug.message(2, "Loading catalog", fileName);
841    catalogManager.debug.message(4, "Default BASE", base.toString());
842
843    fileName = base.toString();
844
845    DataInputStream inStream = null;
846    boolean parsed = false;
847    boolean notFound = false;
848
849    for (int count = 0; !parsed && count < readerArr.size(); count++) {
850      CatalogReader reader = (CatalogReader) readerArr.get(count);
851
852      try {
853        notFound = false;
854        inStream = new DataInputStream(base.openStream());
855      } catch (FileNotFoundException fnfe) {
856        // No catalog; give up!
857        notFound = true;
858        break;
859      }
860
861      try {
862        reader.readCatalog(this, inStream);
863        parsed = true;
864      } catch (CatalogException ce) {
865        if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
866          // give up!
867          break;
868        } else {
869          // try again!
870        }
871      }
872
873      try {
874        inStream.close();
875      } catch (IOException e) {
876        //nop
877      }
878    }
879
880    if (!parsed) {
881      if (notFound) {
882        catalogManager.debug.message(3, "Catalog does not exist", fileName);
883      } else {
884        catalogManager.debug.message(1, "Failed to parse catalog", fileName);
885      }
886    }
887  }
888
889  /**
890   * Cleanup and process a Catalog entry.
891   *
892   * <p>This method processes each Catalog entry, changing mapped
893   * relative system identifiers into absolute ones (based on the current
894   * base URI), and maintaining other information about the current
895   * catalog.</p>
896   *
897   * @param entry The CatalogEntry to process.
898   */
899  public void addEntry(CatalogEntry entry) {
900    int type = entry.getEntryType();
901
902    if (type == BASE) {
903      String value = entry.getEntryArg(0);
904      URL newbase = null;
905
906      if (base == null) {
907        catalogManager.debug.message(5, "BASE CUR", "null");
908      } else {
909        catalogManager.debug.message(5, "BASE CUR", base.toString());
910      }
911      catalogManager.debug.message(4, "BASE STR", value);
912
913      try {
914        value = fixSlashes(value);
915        newbase = new URL(base, value);
916      } catch (MalformedURLException e) {
917        try {
918          newbase = new URL("file:" + value);
919        } catch (MalformedURLException e2) {
920          catalogManager.debug.message(1, "Malformed URL on base", value);
921          newbase = null;
922        }
923      }
924
925      if (newbase != null) {
926        base = newbase;
927      }
928
929      catalogManager.debug.message(5, "BASE NEW", base.toString());
930    } else if (type == CATALOG) {
931      String fsi = makeAbsolute(entry.getEntryArg(0));
932
933      catalogManager.debug.message(4, "CATALOG", fsi);
934
935      localCatalogFiles.addElement(fsi);
936    } else if (type == PUBLIC) {
937      String publicid = PublicId.normalize(entry.getEntryArg(0));
938      String systemid = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
939
940      entry.setEntryArg(0, publicid);
941      entry.setEntryArg(1, systemid);
942
943      catalogManager.debug.message(4, "PUBLIC", publicid, systemid);
944
945      catalogEntries.addElement(entry);
946    } else if (type == SYSTEM) {
947      String systemid = normalizeURI(entry.getEntryArg(0));
948      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
949
950      entry.setEntryArg(1, fsi);
951
952      catalogManager.debug.message(4, "SYSTEM", systemid, fsi);
953
954      catalogEntries.addElement(entry);
955    } else if (type == URI) {
956      String uri = normalizeURI(entry.getEntryArg(0));
957      String altURI = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
958
959      entry.setEntryArg(1, altURI);
960
961      catalogManager.debug.message(4, "URI", uri, altURI);
962
963      catalogEntries.addElement(entry);
964    } else if (type == DOCUMENT) {
965      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
966      entry.setEntryArg(0, fsi);
967
968      catalogManager.debug.message(4, "DOCUMENT", fsi);
969
970      catalogEntries.addElement(entry);
971    } else if (type == OVERRIDE) {
972      catalogManager.debug.message(4, "OVERRIDE", entry.getEntryArg(0));
973
974      catalogEntries.addElement(entry);
975    } else if (type == SGMLDECL) {
976      // meaningless in XML
977      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
978      entry.setEntryArg(0, fsi);
979
980      catalogManager.debug.message(4, "SGMLDECL", fsi);
981
982      catalogEntries.addElement(entry);
983    } else if (type == DELEGATE_PUBLIC) {
984      String ppi = PublicId.normalize(entry.getEntryArg(0));
985      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
986
987      entry.setEntryArg(0, ppi);
988      entry.setEntryArg(1, fsi);
989
990      catalogManager.debug.message(4, "DELEGATE_PUBLIC", ppi, fsi);
991
992      addDelegate(entry);
993    } else if (type == DELEGATE_SYSTEM) {
994      String psi = normalizeURI(entry.getEntryArg(0));
995      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
996
997      entry.setEntryArg(0, psi);
998      entry.setEntryArg(1, fsi);
999
1000      catalogManager.debug.message(4, "DELEGATE_SYSTEM", psi, fsi);
1001
1002      addDelegate(entry);
1003    } else if (type == DELEGATE_URI) {
1004      String pui = normalizeURI(entry.getEntryArg(0));
1005      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1006
1007      entry.setEntryArg(0, pui);
1008      entry.setEntryArg(1, fsi);
1009
1010      catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi);
1011
1012      addDelegate(entry);
1013    } else if (type == REWRITE_SYSTEM) {
1014      String psi = normalizeURI(entry.getEntryArg(0));
1015      String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1016
1017      entry.setEntryArg(0, psi);
1018      entry.setEntryArg(1, rpx);
1019
1020      catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx);
1021
1022      catalogEntries.addElement(entry);
1023    } else if (type == REWRITE_URI) {
1024      String pui = normalizeURI(entry.getEntryArg(0));
1025      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1026
1027      entry.setEntryArg(0, pui);
1028      entry.setEntryArg(1, upx);
1029
1030      catalogManager.debug.message(4, "REWRITE_URI", pui, upx);
1031
1032      catalogEntries.addElement(entry);
1033    } else if (type == SYSTEM_SUFFIX) {
1034      String pui = normalizeURI(entry.getEntryArg(0));
1035      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1036
1037      entry.setEntryArg(0, pui);
1038      entry.setEntryArg(1, upx);
1039
1040      catalogManager.debug.message(4, "SYSTEM_SUFFIX", pui, upx);
1041
1042      catalogEntries.addElement(entry);
1043    } else if (type == URI_SUFFIX) {
1044      String pui = normalizeURI(entry.getEntryArg(0));
1045      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1046
1047      entry.setEntryArg(0, pui);
1048      entry.setEntryArg(1, upx);
1049
1050      catalogManager.debug.message(4, "URI_SUFFIX", pui, upx);
1051
1052      catalogEntries.addElement(entry);
1053    } else if (type == DOCTYPE) {
1054      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1055      entry.setEntryArg(1, fsi);
1056
1057      catalogManager.debug.message(4, "DOCTYPE", entry.getEntryArg(0), fsi);
1058
1059      catalogEntries.addElement(entry);
1060    } else if (type == DTDDECL) {
1061      // meaningless in XML
1062      String fpi = PublicId.normalize(entry.getEntryArg(0));
1063      entry.setEntryArg(0, fpi);
1064      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1065      entry.setEntryArg(1, fsi);
1066
1067      catalogManager.debug.message(4, "DTDDECL", fpi, fsi);
1068
1069      catalogEntries.addElement(entry);
1070    } else if (type == ENTITY) {
1071      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1072      entry.setEntryArg(1, fsi);
1073
1074      catalogManager.debug.message(4, "ENTITY", entry.getEntryArg(0), fsi);
1075
1076      catalogEntries.addElement(entry);
1077    } else if (type == LINKTYPE) {
1078      // meaningless in XML
1079      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1080      entry.setEntryArg(1, fsi);
1081
1082      catalogManager.debug.message(4, "LINKTYPE", entry.getEntryArg(0), fsi);
1083
1084      catalogEntries.addElement(entry);
1085    } else if (type == NOTATION) {
1086      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1087      entry.setEntryArg(1, fsi);
1088
1089      catalogManager.debug.message(4, "NOTATION", entry.getEntryArg(0), fsi);
1090
1091      catalogEntries.addElement(entry);
1092    } else {
1093      catalogEntries.addElement(entry);
1094    }
1095  }
1096
1097  /**
1098   * Handle unknown CatalogEntry types.
1099   *
1100   * <p>This method exists to allow subclasses to deal with unknown
1101   * entry types.</p>
1102   */
1103  public void unknownEntry(Vector strings) {
1104    if (strings != null && strings.size() > 0) {
1105      String keyword = (String) strings.elementAt(0);
1106      catalogManager.debug.message(2, "Unrecognized token parsing catalog", keyword);
1107    }
1108  }
1109
1110  /**
1111   * Parse all subordinate catalogs.
1112   *
1113   * <p>This method recursively parses all of the subordinate catalogs.
1114   * If this method does not throw an exception, you can be confident that
1115   * no subsequent call to any resolve*() method will either, with two
1116   * possible exceptions:</p>
1117   *
1118   * <ol>
1119   * <li><p>Delegated catalogs are re-parsed each time they are needed
1120   * (because a variable list of them may be needed in each case,
1121   * depending on the length of the matching partial public identifier).</p>
1122   * <p>But they are parsed by this method, so as long as they don't
1123   * change or disappear while the program is running, they shouldn't
1124   * generate errors later if they don't generate errors now.</p>
1125   * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
1126   * won't be loaded until they are needed or until you call
1127   * <code>parseAllCatalogs</code> again.</p>
1128   * </ol>
1129   *
1130   * <p>On the other hand, if you don't call this method, you may
1131   * successfully parse documents without having to load all possible
1132   * catalogs.</p>
1133   *
1134   * @throws MalformedURLException The filename (URL) for a
1135   * subordinate or delegated catalog is not a valid URL.
1136   * @throws IOException Error reading some subordinate or delegated
1137   * catalog file.
1138   */
1139  public void parseAllCatalogs()
1140    throws MalformedURLException, IOException {
1141
1142    // Parse all the subordinate catalogs
1143    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
1144      Catalog c = null;
1145
1146      try {
1147        c = (Catalog) catalogs.elementAt(catPos);
1148      } catch (ClassCastException e) {
1149        String catfile = (String) catalogs.elementAt(catPos);
1150        c = newCatalog();
1151
1152        c.parseCatalog(catfile);
1153        catalogs.setElementAt(c, catPos);
1154        c.parseAllCatalogs();
1155      }
1156    }
1157
1158    // Parse all the DELEGATE catalogs
1159    Enumeration en = catalogEntries.elements();
1160    while (en.hasMoreElements()) {
1161      CatalogEntry e = (CatalogEntry) en.nextElement();
1162      if (e.getEntryType() == DELEGATE_PUBLIC
1163          || e.getEntryType() == DELEGATE_SYSTEM
1164          || e.getEntryType() == DELEGATE_URI) {
1165        Catalog dcat = newCatalog();
1166        dcat.parseCatalog(e.getEntryArg(1));
1167      }
1168    }
1169  }
1170
1171
1172  /**
1173   * Return the applicable DOCTYPE system identifier.
1174   *
1175   * @param entityName The name of the entity (element) for which
1176   * a doctype is required.
1177   * @param publicId The nominal public identifier for the doctype
1178   * (as provided in the source document).
1179   * @param systemId The nominal system identifier for the doctype
1180   * (as provided in the source document).
1181   *
1182   * @return The system identifier to use for the doctype.
1183   *
1184   * @throws MalformedURLException The formal system identifier of a
1185   * subordinate catalog cannot be turned into a valid URL.
1186   * @throws IOException Error reading subordinate catalog file.
1187   */
1188  public String resolveDoctype(String entityName,
1189                               String publicId,
1190                               String systemId)
1191    throws MalformedURLException, IOException {
1192    String resolved = null;
1193
1194    catalogManager.debug.message(3, "resolveDoctype("
1195                  +entityName+","+publicId+","+systemId+")");
1196
1197    systemId = normalizeURI(systemId);
1198
1199    if (publicId != null && publicId.startsWith("urn:publicid:")) {
1200      publicId = PublicId.decodeURN(publicId);
1201    }
1202
1203    if (systemId != null && systemId.startsWith("urn:publicid:")) {
1204      systemId = PublicId.decodeURN(systemId);
1205      if (publicId != null && !publicId.equals(systemId)) {
1206        catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1207        systemId = null;
1208      } else {
1209        publicId = systemId;
1210        systemId = null;
1211      }
1212    }
1213
1214    if (systemId != null) {
1215      // If there's a SYSTEM entry in this catalog, use it
1216      resolved = resolveLocalSystem(systemId);
1217      if (resolved != null) {
1218        return resolved;
1219      }
1220    }
1221
1222    if (publicId != null) {
1223      // If there's a PUBLIC entry in this catalog, use it
1224      resolved = resolveLocalPublic(DOCTYPE,
1225                                    entityName,
1226                                    publicId,
1227                                    systemId);
1228      if (resolved != null) {
1229        return resolved;
1230      }
1231    }
1232
1233    // If there's a DOCTYPE entry in this catalog, use it
1234    boolean over = default_override;
1235    Enumeration en = catalogEntries.elements();
1236    while (en.hasMoreElements()) {
1237      CatalogEntry e = (CatalogEntry) en.nextElement();
1238      if (e.getEntryType() == OVERRIDE) {
1239        over = e.getEntryArg(0).equalsIgnoreCase("YES");
1240        continue;
1241      }
1242
1243      if (e.getEntryType() == DOCTYPE
1244          && e.getEntryArg(0).equals(entityName)) {
1245        if (over || systemId == null) {
1246          return e.getEntryArg(1);
1247        }
1248      }
1249    }
1250
1251    // Otherwise, look in the subordinate catalogs
1252    return resolveSubordinateCatalogs(DOCTYPE,
1253                                      entityName,
1254                                      publicId,
1255                                      systemId);
1256  }
1257
1258  /**
1259   * Return the applicable DOCUMENT entry.
1260   *
1261   * @return The system identifier to use for the doctype.
1262   *
1263   * @throws MalformedURLException The formal system identifier of a
1264   * subordinate catalog cannot be turned into a valid URL.
1265   * @throws IOException Error reading subordinate catalog file.
1266   */
1267  public String resolveDocument()
1268    throws MalformedURLException, IOException {
1269    // If there's a DOCUMENT entry, return it
1270
1271    catalogManager.debug.message(3, "resolveDocument");
1272
1273    Enumeration en = catalogEntries.elements();
1274    while (en.hasMoreElements()) {
1275      CatalogEntry e = (CatalogEntry) en.nextElement();
1276      if (e.getEntryType() == DOCUMENT) {
1277        return e.getEntryArg(0);
1278      }
1279    }
1280
1281    return resolveSubordinateCatalogs(DOCUMENT,
1282                                      null, null, null);
1283  }
1284
1285  /**
1286   * Return the applicable ENTITY system identifier.
1287   *
1288   * @param entityName The name of the entity for which
1289   * a system identifier is required.
1290   * @param publicId The nominal public identifier for the entity
1291   * (as provided in the source document).
1292   * @param systemId The nominal system identifier for the entity
1293   * (as provided in the source document).
1294   *
1295   * @return The system identifier to use for the entity.
1296   *
1297   * @throws MalformedURLException The formal system identifier of a
1298   * subordinate catalog cannot be turned into a valid URL.
1299   * @throws IOException Error reading subordinate catalog file.
1300   */
1301  public String resolveEntity(String entityName,
1302                              String publicId,
1303                              String systemId)
1304    throws MalformedURLException, IOException {
1305    String resolved = null;
1306
1307    catalogManager.debug.message(3, "resolveEntity("
1308                  +entityName+","+publicId+","+systemId+")");
1309
1310    systemId = normalizeURI(systemId);
1311
1312    if (publicId != null && publicId.startsWith("urn:publicid:")) {
1313      publicId = PublicId.decodeURN(publicId);
1314    }
1315
1316    if (systemId != null && systemId.startsWith("urn:publicid:")) {
1317      systemId = PublicId.decodeURN(systemId);
1318      if (publicId != null && !publicId.equals(systemId)) {
1319        catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1320        systemId = null;
1321      } else {
1322        publicId = systemId;
1323        systemId = null;
1324      }
1325    }
1326
1327    if (systemId != null) {
1328      // If there's a SYSTEM entry in this catalog, use it
1329      resolved = resolveLocalSystem(systemId);
1330      if (resolved != null) {
1331        return resolved;
1332      }
1333    }
1334
1335    if (publicId != null) {
1336      // If there's a PUBLIC entry in this catalog, use it
1337      resolved = resolveLocalPublic(ENTITY,
1338                                    entityName,
1339                                    publicId,
1340                                    systemId);
1341      if (resolved != null) {
1342        return resolved;
1343      }
1344    }
1345
1346    // If there's a ENTITY entry in this catalog, use it
1347    boolean over = default_override;
1348    Enumeration en = catalogEntries.elements();
1349    while (en.hasMoreElements()) {
1350      CatalogEntry e = (CatalogEntry) en.nextElement();
1351      if (e.getEntryType() == OVERRIDE) {
1352        over = e.getEntryArg(0).equalsIgnoreCase("YES");
1353        continue;
1354      }
1355
1356      if (e.getEntryType() == ENTITY
1357          && e.getEntryArg(0).equals(entityName)) {
1358        if (over || systemId == null) {
1359          return e.getEntryArg(1);
1360        }
1361      }
1362    }
1363
1364    // Otherwise, look in the subordinate catalogs
1365    return resolveSubordinateCatalogs(ENTITY,
1366                                      entityName,
1367                                      publicId,
1368                                      systemId);
1369  }
1370
1371  /**
1372   * Return the applicable NOTATION system identifier.
1373   *
1374   * @param notationName The name of the notation for which
1375   * a doctype is required.
1376   * @param publicId The nominal public identifier for the notation
1377   * (as provided in the source document).
1378   * @param systemId The nominal system identifier for the notation
1379   * (as provided in the source document).
1380   *
1381   * @return The system identifier to use for the notation.
1382   *
1383   * @throws MalformedURLException The formal system identifier of a
1384   * subordinate catalog cannot be turned into a valid URL.
1385   * @throws IOException Error reading subordinate catalog file.
1386   */
1387  public String resolveNotation(String notationName,
1388                                String publicId,
1389                                String systemId)
1390    throws MalformedURLException, IOException {
1391    String resolved = null;
1392
1393    catalogManager.debug.message(3, "resolveNotation("
1394                  +notationName+","+publicId+","+systemId+")");
1395
1396    systemId = normalizeURI(systemId);
1397
1398    if (publicId != null && publicId.startsWith("urn:publicid:")) {
1399      publicId = PublicId.decodeURN(publicId);
1400    }
1401
1402    if (systemId != null && systemId.startsWith("urn:publicid:")) {
1403      systemId = PublicId.decodeURN(systemId);
1404      if (publicId != null && !publicId.equals(systemId)) {
1405        catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1406        systemId = null;
1407      } else {
1408        publicId = systemId;
1409        systemId = null;
1410      }
1411    }
1412
1413    if (systemId != null) {
1414      // If there's a SYSTEM entry in this catalog, use it
1415      resolved = resolveLocalSystem(systemId);
1416      if (resolved != null) {
1417        return resolved;
1418      }
1419    }
1420
1421    if (publicId != null) {
1422      // If there's a PUBLIC entry in this catalog, use it
1423      resolved = resolveLocalPublic(NOTATION,
1424                                    notationName,
1425                                    publicId,
1426                                    systemId);
1427      if (resolved != null) {
1428        return resolved;
1429      }
1430    }
1431
1432    // If there's a NOTATION entry in this catalog, use it
1433    boolean over = default_override;
1434    Enumeration en = catalogEntries.elements();
1435    while (en.hasMoreElements()) {
1436      CatalogEntry e = (CatalogEntry) en.nextElement();
1437      if (e.getEntryType() == OVERRIDE) {
1438        over = e.getEntryArg(0).equalsIgnoreCase("YES");
1439        continue;
1440      }
1441
1442      if (e.getEntryType() == NOTATION
1443          && e.getEntryArg(0).equals(notationName)) {
1444        if (over || systemId == null) {
1445          return e.getEntryArg(1);
1446        }
1447      }
1448    }
1449
1450    // Otherwise, look in the subordinate catalogs
1451    return resolveSubordinateCatalogs(NOTATION,
1452                                      notationName,
1453                                      publicId,
1454                                      systemId);
1455  }
1456
1457  /**
1458   * Return the applicable PUBLIC or SYSTEM identifier.
1459   *
1460   * <p>This method searches the Catalog and returns the system
1461   * identifier specified for the given system or
1462   * public identifiers. If
1463   * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1464   * null is returned.</p>
1465   *
1466   * @param publicId The public identifier to locate in the catalog.
1467   * Public identifiers are normalized before comparison.
1468   * @param systemId The nominal system identifier for the entity
1469   * in question (as provided in the source document).
1470   *
1471   * @throws MalformedURLException The formal system identifier of a
1472   * subordinate catalog cannot be turned into a valid URL.
1473   * @throws IOException Error reading subordinate catalog file.
1474   *
1475   * @return The system identifier to use.
1476   * Note that the nominal system identifier is not returned if a
1477   * match is not found in the catalog, instead null is returned
1478   * to indicate that no match was found.
1479   */
1480  public String resolvePublic(String publicId, String systemId)
1481    throws MalformedURLException, IOException {
1482
1483    catalogManager.debug.message(3, "resolvePublic("+publicId+","+systemId+")");
1484
1485    systemId = normalizeURI(systemId);
1486
1487    if (publicId != null && publicId.startsWith("urn:publicid:")) {
1488      publicId = PublicId.decodeURN(publicId);
1489    }
1490
1491    if (systemId != null && systemId.startsWith("urn:publicid:")) {
1492      systemId = PublicId.decodeURN(systemId);
1493      if (publicId != null && !publicId.equals(systemId)) {
1494        catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
1495        systemId = null;
1496      } else {
1497        publicId = systemId;
1498        systemId = null;
1499      }
1500    }
1501
1502    // If there's a SYSTEM entry in this catalog, use it
1503    if (systemId != null) {
1504      String resolved = resolveLocalSystem(systemId);
1505      if (resolved != null) {
1506        return resolved;
1507      }
1508    }
1509
1510    // If there's a PUBLIC entry in this catalog, use it
1511    String resolved = resolveLocalPublic(PUBLIC,
1512                                         null,
1513                                         publicId,
1514                                         systemId);
1515    if (resolved != null) {
1516      return resolved;
1517    }
1518
1519    // Otherwise, look in the subordinate catalogs
1520    return resolveSubordinateCatalogs(PUBLIC,
1521                                      null,
1522                                      publicId,
1523                                      systemId);
1524  }
1525
1526  /**
1527   * Return the applicable PUBLIC or SYSTEM identifier.
1528   *
1529   * <p>This method searches the Catalog and returns the system
1530   * identifier specified for the given system or public identifiers.
1531   * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1532   * delegated Catalogs are interrogated.</p>
1533   *
1534   * <p>There are four possible cases:</p>
1535   *
1536   * <ul>
1537   * <li>If the system identifier provided matches a SYSTEM entry
1538   * in the current catalog, the SYSTEM entry is returned.
1539   * <li>If the system identifier is not null, the PUBLIC entries
1540   * that were encountered when OVERRIDE YES was in effect are
1541   * interrogated and the first matching entry is returned.</li>
1542   * <li>If the system identifier is null, then all of the PUBLIC
1543   * entries are interrogated and the first matching entry
1544   * is returned. This may not be the same as the preceding case, if
1545   * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
1546   * XML, the only place where a public identifier may occur without
1547   * a system identifier is in a notation declaration.</li>
1548   * <li>Finally, if the public identifier matches one of the partial
1549   * public identifiers specified in a DELEGATE* entry in
1550   * the Catalog, the delegated catalog is interrogated. The first
1551   * time that the delegated catalog is required, it will be
1552   * retrieved and parsed. It is subsequently cached.
1553   * </li>
1554   * </ul>
1555   *
1556   * @param entityType The CatalogEntry type for which this query is
1557   * being conducted. This is necessary in order to do the approprate
1558   * query on a delegated catalog.
1559   * @param entityName The name of the entity being searched for, if
1560   * appropriate.
1561   * @param publicId The public identifier of the entity in question.
1562   * @param systemId The nominal system identifier for the entity
1563   * in question (as provided in the source document).
1564   *
1565   * @throws MalformedURLException The formal system identifier of a
1566   * delegated catalog cannot be turned into a valid URL.
1567   * @throws IOException Error reading delegated catalog file.
1568   *
1569   * @return The system identifier to use.
1570   * Note that the nominal system identifier is not returned if a
1571   * match is not found in the catalog, instead null is returned
1572   * to indicate that no match was found.
1573   */
1574  protected synchronized String resolveLocalPublic(int entityType,
1575                                                   String entityName,
1576                                                   String publicId,
1577                                                   String systemId)
1578    throws MalformedURLException, IOException {
1579
1580    // Always normalize the public identifier before attempting a match
1581    publicId = PublicId.normalize(publicId);
1582
1583    // If there's a SYSTEM entry in this catalog, use it
1584    if (systemId != null) {
1585      String resolved = resolveLocalSystem(systemId);
1586      if (resolved != null) {
1587        return resolved;
1588      }
1589    }
1590
1591    // If there's a PUBLIC entry in this catalog, use it
1592    boolean over = default_override;
1593    Enumeration en = catalogEntries.elements();
1594    while (en.hasMoreElements()) {
1595      CatalogEntry e = (CatalogEntry) en.nextElement();
1596      if (e.getEntryType() == OVERRIDE) {
1597        over = e.getEntryArg(0).equalsIgnoreCase("YES");
1598        continue;
1599      }
1600
1601      if (e.getEntryType() == PUBLIC
1602          && e.getEntryArg(0).equals(publicId)) {
1603        if (over || systemId == null) {
1604          return e.getEntryArg(1);
1605        }
1606      }
1607    }
1608
1609    // If there's a DELEGATE_PUBLIC entry in this catalog, use it
1610    over = default_override;
1611    en = catalogEntries.elements();
1612    Vector delCats = new Vector();
1613    while (en.hasMoreElements()) {
1614      CatalogEntry e = (CatalogEntry) en.nextElement();
1615      if (e.getEntryType() == OVERRIDE) {
1616        over = e.getEntryArg(0).equalsIgnoreCase("YES");
1617        continue;
1618      }
1619
1620      if (e.getEntryType() == DELEGATE_PUBLIC
1621          && (over || systemId == null)) {
1622        String p = (String) e.getEntryArg(0);
1623        if (p.length() <= publicId.length()
1624            && p.equals(publicId.substring(0, p.length()))) {
1625          // delegate this match to the other catalog
1626
1627          delCats.addElement(e.getEntryArg(1));
1628        }
1629      }
1630    }
1631
1632    if (delCats.size() > 0) {
1633      Enumeration enCats = delCats.elements();
1634
1635      if (catalogManager.debug.getDebug() > 1) {
1636        catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1637        while (enCats.hasMoreElements()) {
1638          String delegatedCatalog = (String) enCats.nextElement();
1639          catalogManager.debug.message(2, "\t" + delegatedCatalog);
1640        }
1641      }
1642
1643      Catalog dcat = newCatalog();
1644
1645      enCats = delCats.elements();
1646      while (enCats.hasMoreElements()) {
1647        String delegatedCatalog = (String) enCats.nextElement();
1648        dcat.parseCatalog(delegatedCatalog);
1649      }
1650
1651      return dcat.resolvePublic(publicId, null);
1652    }
1653
1654    // Nada!
1655    return null;
1656  }
1657
1658  /**
1659   * Return the applicable SYSTEM system identifier.
1660   *
1661   * <p>If a SYSTEM entry exists in the Catalog
1662   * for the system ID specified, return the mapped value.</p>
1663   *
1664   * <p>On Windows-based operating systems, the comparison between
1665   * the system identifier provided and the SYSTEM entries in the
1666   * Catalog is case-insensitive.</p>
1667   *
1668   * @param systemId The system ID to locate in the catalog.
1669   *
1670   * @return The resolved system identifier.
1671   *
1672   * @throws MalformedURLException The formal system identifier of a
1673   * subordinate catalog cannot be turned into a valid URL.
1674   * @throws IOException Error reading subordinate catalog file.
1675   */
1676  public String resolveSystem(String systemId)
1677    throws MalformedURLException, IOException {
1678
1679    catalogManager.debug.message(3, "resolveSystem("+systemId+")");
1680
1681    systemId = normalizeURI(systemId);
1682
1683    if (systemId != null && systemId.startsWith("urn:publicid:")) {
1684      systemId = PublicId.decodeURN(systemId);
1685      return resolvePublic(systemId, null);
1686    }
1687
1688    // If there's a SYSTEM entry in this catalog, use it
1689    if (systemId != null) {
1690      String resolved = resolveLocalSystem(systemId);
1691      if (resolved != null) {
1692        return resolved;
1693      }
1694    }
1695
1696    // Otherwise, look in the subordinate catalogs
1697    return resolveSubordinateCatalogs(SYSTEM,
1698                                      null,
1699                                      null,
1700                                      systemId);
1701  }
1702
1703  /**
1704   * Return the applicable SYSTEM system identifier in this
1705   * catalog.
1706   *
1707   * <p>If a SYSTEM entry exists in the catalog file
1708   * for the system ID specified, return the mapped value.</p>
1709   *
1710   * @param systemId The system ID to locate in the catalog
1711   *
1712   * @return The mapped system identifier or null
1713   */
1714  protected String resolveLocalSystem(String systemId)
1715    throws MalformedURLException, IOException {
1716
1717    String osname = SecuritySupport.getSystemProperty("os.name");
1718    boolean windows = (osname.indexOf("Windows") >= 0);
1719    Enumeration en = catalogEntries.elements();
1720    while (en.hasMoreElements()) {
1721      CatalogEntry e = (CatalogEntry) en.nextElement();
1722      if (e.getEntryType() == SYSTEM
1723          && (e.getEntryArg(0).equals(systemId)
1724              || (windows
1725                  && e.getEntryArg(0).equalsIgnoreCase(systemId)))) {
1726        return e.getEntryArg(1);
1727      }
1728    }
1729
1730    // If there's a REWRITE_SYSTEM entry in this catalog, use it
1731    en = catalogEntries.elements();
1732    String startString = null;
1733    String prefix = null;
1734    while (en.hasMoreElements()) {
1735      CatalogEntry e = (CatalogEntry) en.nextElement();
1736
1737      if (e.getEntryType() == REWRITE_SYSTEM) {
1738        String p = (String) e.getEntryArg(0);
1739        if (p.length() <= systemId.length()
1740            && p.equals(systemId.substring(0, p.length()))) {
1741          // Is this the longest prefix?
1742          if (startString == null
1743              || p.length() > startString.length()) {
1744            startString = p;
1745            prefix = e.getEntryArg(1);
1746          }
1747        }
1748      }
1749    }
1750
1751    if (prefix != null) {
1752      // return the systemId with the new prefix
1753      return prefix + systemId.substring(startString.length());
1754    }
1755
1756    // If there's a SYSTEM_SUFFIX entry in this catalog, use it
1757    en = catalogEntries.elements();
1758    String suffixString = null;
1759    String suffixURI = null;
1760    while (en.hasMoreElements()) {
1761      CatalogEntry e = (CatalogEntry) en.nextElement();
1762
1763      if (e.getEntryType() == SYSTEM_SUFFIX) {
1764        String p = (String) e.getEntryArg(0);
1765        if (p.length() <= systemId.length()
1766            && systemId.endsWith(p)) {
1767          // Is this the longest prefix?
1768          if (suffixString == null
1769              || p.length() > suffixString.length()) {
1770            suffixString = p;
1771            suffixURI = e.getEntryArg(1);
1772          }
1773        }
1774      }
1775    }
1776
1777    if (suffixURI != null) {
1778      // return the systemId for the suffix
1779      return suffixURI;
1780    }
1781
1782    // If there's a DELEGATE_SYSTEM entry in this catalog, use it
1783    en = catalogEntries.elements();
1784    Vector delCats = new Vector();
1785    while (en.hasMoreElements()) {
1786      CatalogEntry e = (CatalogEntry) en.nextElement();
1787
1788      if (e.getEntryType() == DELEGATE_SYSTEM) {
1789        String p = (String) e.getEntryArg(0);
1790        if (p.length() <= systemId.length()
1791            && p.equals(systemId.substring(0, p.length()))) {
1792          // delegate this match to the other catalog
1793
1794          delCats.addElement(e.getEntryArg(1));
1795        }
1796      }
1797    }
1798
1799    if (delCats.size() > 0) {
1800      Enumeration enCats = delCats.elements();
1801
1802      if (catalogManager.debug.getDebug() > 1) {
1803        catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1804        while (enCats.hasMoreElements()) {
1805          String delegatedCatalog = (String) enCats.nextElement();
1806          catalogManager.debug.message(2, "\t" + delegatedCatalog);
1807        }
1808      }
1809
1810      Catalog dcat = newCatalog();
1811
1812      enCats = delCats.elements();
1813      while (enCats.hasMoreElements()) {
1814        String delegatedCatalog = (String) enCats.nextElement();
1815        dcat.parseCatalog(delegatedCatalog);
1816      }
1817
1818      return dcat.resolveSystem(systemId);
1819    }
1820
1821    return null;
1822  }
1823
1824  /**
1825   * Return the applicable URI.
1826   *
1827   * <p>If a URI entry exists in the Catalog
1828   * for the URI specified, return the mapped value.</p>
1829   *
1830   * <p>URI comparison is case sensitive.</p>
1831   *
1832   * @param uri The URI to locate in the catalog.
1833   *
1834   * @return The resolved URI.
1835   *
1836   * @throws MalformedURLException The system identifier of a
1837   * subordinate catalog cannot be turned into a valid URL.
1838   * @throws IOException Error reading subordinate catalog file.
1839   */
1840  public String resolveURI(String uri)
1841    throws MalformedURLException, IOException {
1842
1843    catalogManager.debug.message(3, "resolveURI("+uri+")");
1844
1845    uri = normalizeURI(uri);
1846
1847    if (uri != null && uri.startsWith("urn:publicid:")) {
1848      uri = PublicId.decodeURN(uri);
1849      return resolvePublic(uri, null);
1850    }
1851
1852    // If there's a URI entry in this catalog, use it
1853    if (uri != null) {
1854      String resolved = resolveLocalURI(uri);
1855      if (resolved != null) {
1856        return resolved;
1857      }
1858    }
1859
1860    // Otherwise, look in the subordinate catalogs
1861    return resolveSubordinateCatalogs(URI,
1862                                      null,
1863                                      null,
1864                                      uri);
1865  }
1866
1867  /**
1868   * Return the applicable URI in this catalog.
1869   *
1870   * <p>If a URI entry exists in the catalog file
1871   * for the URI specified, return the mapped value.</p>
1872   *
1873   * @param uri The URI to locate in the catalog
1874   *
1875   * @return The mapped URI or null
1876   */
1877  protected String resolveLocalURI(String uri)
1878    throws MalformedURLException, IOException {
1879    Enumeration en = catalogEntries.elements();
1880    while (en.hasMoreElements()) {
1881      CatalogEntry e = (CatalogEntry) en.nextElement();
1882      if (e.getEntryType() == URI
1883          && (e.getEntryArg(0).equals(uri))) {
1884        return e.getEntryArg(1);
1885      }
1886    }
1887
1888    // If there's a REWRITE_URI entry in this catalog, use it
1889    en = catalogEntries.elements();
1890    String startString = null;
1891    String prefix = null;
1892    while (en.hasMoreElements()) {
1893      CatalogEntry e = (CatalogEntry) en.nextElement();
1894
1895      if (e.getEntryType() == REWRITE_URI) {
1896        String p = (String) e.getEntryArg(0);
1897        if (p.length() <= uri.length()
1898            && p.equals(uri.substring(0, p.length()))) {
1899          // Is this the longest prefix?
1900          if (startString == null
1901              || p.length() > startString.length()) {
1902            startString = p;
1903            prefix = e.getEntryArg(1);
1904          }
1905        }
1906      }
1907    }
1908
1909    if (prefix != null) {
1910      // return the uri with the new prefix
1911      return prefix + uri.substring(startString.length());
1912    }
1913
1914    // If there's a URI_SUFFIX entry in this catalog, use it
1915    en = catalogEntries.elements();
1916    String suffixString = null;
1917    String suffixURI = null;
1918    while (en.hasMoreElements()) {
1919      CatalogEntry e = (CatalogEntry) en.nextElement();
1920
1921      if (e.getEntryType() == URI_SUFFIX) {
1922        String p = (String) e.getEntryArg(0);
1923        if (p.length() <= uri.length()
1924            && uri.endsWith(p)) {
1925          // Is this the longest prefix?
1926          if (suffixString == null
1927              || p.length() > suffixString.length()) {
1928            suffixString = p;
1929            suffixURI = e.getEntryArg(1);
1930          }
1931        }
1932      }
1933    }
1934
1935    if (suffixURI != null) {
1936      // return the uri for the suffix
1937      return suffixURI;
1938    }
1939
1940    // If there's a DELEGATE_URI entry in this catalog, use it
1941    en = catalogEntries.elements();
1942    Vector delCats = new Vector();
1943    while (en.hasMoreElements()) {
1944      CatalogEntry e = (CatalogEntry) en.nextElement();
1945
1946      if (e.getEntryType() == DELEGATE_URI) {
1947        String p = (String) e.getEntryArg(0);
1948        if (p.length() <= uri.length()
1949            && p.equals(uri.substring(0, p.length()))) {
1950          // delegate this match to the other catalog
1951
1952          delCats.addElement(e.getEntryArg(1));
1953        }
1954      }
1955    }
1956
1957    if (delCats.size() > 0) {
1958      Enumeration enCats = delCats.elements();
1959
1960      if (catalogManager.debug.getDebug() > 1) {
1961        catalogManager.debug.message(2, "Switching to delegated catalog(s):");
1962        while (enCats.hasMoreElements()) {
1963          String delegatedCatalog = (String) enCats.nextElement();
1964          catalogManager.debug.message(2, "\t" + delegatedCatalog);
1965        }
1966      }
1967
1968      Catalog dcat = newCatalog();
1969
1970      enCats = delCats.elements();
1971      while (enCats.hasMoreElements()) {
1972        String delegatedCatalog = (String) enCats.nextElement();
1973        dcat.parseCatalog(delegatedCatalog);
1974      }
1975
1976      return dcat.resolveURI(uri);
1977    }
1978
1979    return null;
1980  }
1981
1982  /**
1983   * Search the subordinate catalogs, in order, looking for a match.
1984   *
1985   * <p>This method searches the Catalog and returns the system
1986   * identifier specified for the given entity type with the given
1987   * name, public, and system identifiers. In some contexts, these
1988   * may be null.</p>
1989   *
1990   * @param entityType The CatalogEntry type for which this query is
1991   * being conducted. This is necessary in order to do the approprate
1992   * query on a subordinate catalog.
1993   * @param entityName The name of the entity being searched for, if
1994   * appropriate.
1995   * @param publicId The public identifier of the entity in question
1996   * (as provided in the source document).
1997   * @param systemId The nominal system identifier for the entity
1998   * in question (as provided in the source document). This parameter is
1999   * overloaded for the URI entry type.
2000   *
2001   * @throws MalformedURLException The formal system identifier of a
2002   * delegated catalog cannot be turned into a valid URL.
2003   * @throws IOException Error reading delegated catalog file.
2004   *
2005   * @return The system identifier to use.
2006   * Note that the nominal system identifier is not returned if a
2007   * match is not found in the catalog, instead null is returned
2008   * to indicate that no match was found.
2009   */
2010  protected synchronized String resolveSubordinateCatalogs(int entityType,
2011                                                           String entityName,
2012                                                           String publicId,
2013                                                           String systemId)
2014    throws MalformedURLException, IOException {
2015
2016    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
2017      Catalog c = null;
2018
2019      try {
2020        c = (Catalog) catalogs.elementAt(catPos);
2021      } catch (ClassCastException e) {
2022        String catfile = (String) catalogs.elementAt(catPos);
2023        c = newCatalog();
2024
2025        try {
2026          c.parseCatalog(catfile);
2027        } catch (MalformedURLException mue) {
2028          catalogManager.debug.message(1, "Malformed Catalog URL", catfile);
2029        } catch (FileNotFoundException fnfe) {
2030          catalogManager.debug.message(1, "Failed to load catalog, file not found",
2031                        catfile);
2032        } catch (IOException ioe) {
2033          catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile);
2034        }
2035
2036        catalogs.setElementAt(c, catPos);
2037      }
2038
2039      String resolved = null;
2040
2041      // Ok, now what are we supposed to call here?
2042      if (entityType == DOCTYPE) {
2043        resolved = c.resolveDoctype(entityName,
2044                                    publicId,
2045                                    systemId);
2046      } else if (entityType == DOCUMENT) {
2047        resolved = c.resolveDocument();
2048      } else if (entityType == ENTITY) {
2049        resolved = c.resolveEntity(entityName,
2050                                   publicId,
2051                                   systemId);
2052      } else if (entityType == NOTATION) {
2053        resolved = c.resolveNotation(entityName,
2054                                     publicId,
2055                                     systemId);
2056      } else if (entityType == PUBLIC) {
2057        resolved = c.resolvePublic(publicId, systemId);
2058      } else if (entityType == SYSTEM) {
2059        resolved = c.resolveSystem(systemId);
2060      } else if (entityType == URI) {
2061        resolved = c.resolveURI(systemId);
2062      }
2063
2064      if (resolved != null) {
2065        return resolved;
2066      }
2067    }
2068
2069    return null;
2070  }
2071
2072  // -----------------------------------------------------------------
2073
2074  /**
2075   * Replace backslashes with forward slashes. (URLs always use
2076   * forward slashes.)
2077   *
2078   * @param sysid The input system identifier.
2079   * @return The same system identifier with backslashes turned into
2080   * forward slashes.
2081   */
2082  protected String fixSlashes (String sysid) {
2083    return sysid.replace('\\', '/');
2084  }
2085
2086  /**
2087   * Construct an absolute URI from a relative one, using the current
2088   * base URI.
2089   *
2090   * @param sysid The (possibly relative) system identifier
2091   * @return The system identifier made absolute with respect to the
2092   * current {@link #base}.
2093   */
2094  protected String makeAbsolute(String sysid) {
2095    URL local = null;
2096
2097    sysid = fixSlashes(sysid);
2098
2099    try {
2100      local = new URL(base, sysid);
2101    } catch (MalformedURLException e) {
2102      catalogManager.debug.message(1, "Malformed URL on system identifier", sysid);
2103    }
2104
2105    if (local != null) {
2106      return local.toString();
2107    } else {
2108      return sysid;
2109    }
2110  }
2111
2112
2113    /**
2114     * Perform character normalization on a URI reference.
2115     *
2116     * @param uriref The URI reference
2117     * @return The normalized URI reference.
2118     */
2119    protected String normalizeURI(String uriref) {
2120        if (uriref == null) {
2121            return null;
2122        }
2123        final int length = uriref.length();
2124        for (int i = 0; i < length; ++i) {
2125            char c = uriref.charAt(i);
2126            if ((c <= 0x20)    // ctrl
2127                    || (c > 0x7F)  // high ascii
2128                    || (c == 0x22) // "
2129                    || (c == 0x3C) // <
2130                    || (c == 0x3E) // >
2131                    || (c == 0x5C) // \
2132                    || (c == 0x5E) // ^
2133                    || (c == 0x60) // `
2134                    || (c == 0x7B) // {
2135                    || (c == 0x7C) // |
2136                    || (c == 0x7D) // }
2137                    || (c == 0x7F)) {
2138                return normalizeURI(uriref, i);
2139            }
2140        }
2141        return uriref;
2142    }
2143
2144    /**
2145     * Perform character normalization on a URI reference.
2146     *
2147     * @param uriref The URI reference
2148     * @param index The index of the first character which requires escaping.
2149     * @return The normalized URI reference.
2150     */
2151    private String normalizeURI(String uriref, int index) {
2152        final StringBuilder buffer = new StringBuilder();
2153        for (int i = 0; i < index; ++i) {
2154            buffer.append(uriref.charAt(i));
2155        }
2156        final byte[] bytes;
2157        try {
2158            bytes = uriref.substring(index).getBytes("UTF-8");
2159        }
2160        catch (UnsupportedEncodingException uee) {
2161            // this can't happen
2162            catalogManager.debug.message(1, "UTF-8 is an unsupported encoding!?");
2163            return uriref;
2164        }
2165        for (int count = 0; count < bytes.length; ++count) {
2166            int ch = bytes[count] & 0xFF;
2167            if ((ch <= 0x20)    // ctrl
2168                    || (ch > 0x7F)  // high ascii
2169                    || (ch == 0x22) // "
2170                    || (ch == 0x3C) // <
2171                    || (ch == 0x3E) // >
2172                    || (ch == 0x5C) // \
2173                    || (ch == 0x5E) // ^
2174                    || (ch == 0x60) // `
2175                    || (ch == 0x7B) // {
2176                    || (ch == 0x7C) // |
2177                    || (ch == 0x7D) // }
2178                    || (ch == 0x7F)) {
2179                writeEncodedByte(ch, buffer);
2180            }
2181            else {
2182                buffer.append((char) bytes[count]);
2183            }
2184        }
2185        return buffer.toString();
2186    }
2187
2188    /**
2189     * Perform %-encoding on a single byte.
2190     *
2191     * @param b The 8-bit integer that represents the byte. (Bytes are signed
2192     *          but encoding needs to look at the bytes unsigned.)
2193     * @return The %-encoded string for the byte in question.
2194     */
2195    protected String encodedByte(int b) {
2196        StringBuilder buffer = new StringBuilder(3);
2197        writeEncodedByte(b, buffer);
2198        return buffer.toString();
2199    }
2200
2201    /**
2202     * Perform %-encoding on a single byte.
2203     *
2204     * @param b The 8-bit integer that represents the byte. (Bytes are signed
2205     *          but encoding needs to look at the bytes unsigned.)
2206     * @param buffer The target for the %-encoded string for the byte in question.
2207     */
2208    private void writeEncodedByte(int b, StringBuilder buffer) {
2209        String hex = Integer.toHexString(b).toUpperCase(Locale.ENGLISH);
2210        if (hex.length() < 2) {
2211            buffer.append("%0");
2212            buffer.append(hex);
2213        }
2214        else {
2215            buffer.append('%');
2216            buffer.append(hex);
2217        }
2218    }
2219
2220  // -----------------------------------------------------------------
2221
2222  /**
2223   * Add to the current list of delegated catalogs.
2224   *
2225   * <p>This method always constructs the {@link #localDelegate}
2226   * vector so that it is ordered by length of partial
2227   * public identifier.</p>
2228   *
2229   * @param entry The DELEGATE catalog entry
2230   */
2231  protected void addDelegate(CatalogEntry entry) {
2232    int pos = 0;
2233    String partial = entry.getEntryArg(0);
2234
2235    Enumeration local = localDelegate.elements();
2236    while (local.hasMoreElements()) {
2237      CatalogEntry dpe = (CatalogEntry) local.nextElement();
2238      String dp = dpe.getEntryArg(0);
2239      if (dp.equals(partial)) {
2240        // we already have this prefix
2241        return;
2242      }
2243      if (dp.length() > partial.length()) {
2244        pos++;
2245      }
2246      if (dp.length() < partial.length()) {
2247        break;
2248      }
2249    }
2250
2251    // now insert partial into the vector at [pos]
2252    if (localDelegate.size() == 0) {
2253      localDelegate.addElement(entry);
2254    } else {
2255      localDelegate.insertElementAt(entry, pos);
2256    }
2257  }
2258}
2259