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