0001: // Catalog.java - Represents OASIS Open Catalog files.
0002:
0003: /*
0004: * Copyright 2001-2004 The Apache Software Foundation or its licensors,
0005: * as applicable.
0006: *
0007: * Licensed under the Apache License, Version 2.0 (the "License");
0008: * you may not use this file except in compliance with the License.
0009: * You may obtain a copy of the License at
0010: *
0011: * http://www.apache.org/licenses/LICENSE-2.0
0012: *
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS,
0015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016: * See the License for the specific language governing permissions and
0017: * limitations under the License.
0018: */
0019:
0020: package com.sun.org.apache.xml.internal.resolver;
0021:
0022: import java.io.IOException;
0023: import java.io.FileNotFoundException;
0024: import java.io.InputStream;
0025: import java.io.UnsupportedEncodingException;
0026: import java.io.DataInputStream;
0027:
0028: import java.util.Enumeration;
0029: import java.util.Hashtable;
0030: import java.util.Vector;
0031:
0032: import java.net.URL;
0033: import java.net.MalformedURLException;
0034:
0035: import javax.xml.parsers.SAXParserFactory;
0036:
0037: import com.sun.org.apache.xml.internal.resolver.CatalogManager;
0038: import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;
0039: import com.sun.org.apache.xml.internal.resolver.readers.CatalogReader;
0040: import com.sun.org.apache.xml.internal.resolver.readers.SAXCatalogReader;
0041: import com.sun.org.apache.xml.internal.resolver.readers.TR9401CatalogReader;
0042: import com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader;
0043: import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
0044:
0045: /**
0046: * Represents OASIS Open Catalog files.
0047: *
0048: * <p>This class implements the semantics of OASIS Open Catalog files
0049: * (defined by
0050: * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical
0051: * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>).</p>
0052: *
0053: * <p>The primary purpose of the Catalog is to associate resources in the
0054: * document with local system identifiers. Some entities
0055: * (document types, XML entities, and notations) have names and all of them
0056: * can have either public or system identifiers or both. (In XML, only a
0057: * notation can have a public identifier without a system identifier, but
0058: * the methods implemented in this class obey the Catalog semantics
0059: * from the SGML
0060: * days when system identifiers were optional.)</p>
0061: *
0062: * <p>The system identifiers returned by the resolution methods in this
0063: * class are valid, i.e. usable by, and in fact constructed by, the
0064: * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
0065: * somewhat non-standard ways and the system identifiers returned may
0066: * not be directly usable in a browser or filesystem context.
0067: *
0068: * <p>This class recognizes all of the Catalog entries defined in
0069: * TR9401:1997:</p>
0070: *
0071: * <ul>
0072: * <li><b>BASE</b>
0073: * changes the base URI for resolving relative system identifiers. The
0074: * initial base URI is the URI of the location of the catalog (which is,
0075: * in turn, relative to the location of the current working directory
0076: * at startup, as returned by the <tt>user.dir</tt> system property).</li>
0077: * <li><b>CATALOG</b>
0078: * processes other catalog files. An included catalog occurs logically
0079: * at the end of the including catalog.</li>
0080: * <li><b>DELEGATE_PUBLIC</b>
0081: * specifies alternate catalogs for some public identifiers. The delegated
0082: * catalogs are not loaded until they are needed, but they are cached
0083: * once loaded.</li>
0084: * <li><b>DELEGATE_SYSTEM</b>
0085: * specifies alternate catalogs for some system identifiers. The delegated
0086: * catalogs are not loaded until they are needed, but they are cached
0087: * once loaded.</li>
0088: * <li><b>DELEGATE_URI</b>
0089: * specifies alternate catalogs for some URIs. The delegated
0090: * catalogs are not loaded until they are needed, but they are cached
0091: * once loaded.</li>
0092: * <li><b>REWRITE_SYSTEM</b>
0093: * specifies alternate prefix for a system identifier.</li>
0094: * <li><b>REWRITE_URI</b>
0095: * specifies alternate prefix for a URI.</li>
0096: * <li><b>SYSTEM_SUFFIX</b>
0097: * maps any system identifier that ends with a particular suffix to another
0098: * system identifier.</li>
0099: * <li><b>URI_SUFFIX</b>
0100: * maps any URI that ends with a particular suffix to another URI.</li>
0101: * <li><b>DOCTYPE</b>
0102: * associates the names of root elements with URIs. (In other words, an XML
0103: * processor might infer the doctype of an XML document that does not include
0104: * a doctype declaration by looking for the DOCTYPE entry in the
0105: * catalog which matches the name of the root element of the document.)</li>
0106: * <li><b>DOCUMENT</b>
0107: * provides a default document.</li>
0108: * <li><b>DTDDECL</b>
0109: * recognized and silently ignored. Not relevant for XML.</li>
0110: * <li><b>ENTITY</b>
0111: * associates entity names with URIs.</li>
0112: * <li><b>LINKTYPE</b>
0113: * recognized and silently ignored. Not relevant for XML.</li>
0114: * <li><b>NOTATION</b>
0115: * associates notation names with URIs.</li>
0116: * <li><b>OVERRIDE</b>
0117: * changes the override behavior. Initial behavior is set by the
0118: * system property <tt>xml.catalog.override</tt>. The default initial
0119: * behavior is 'YES', that is, entries in the catalog override
0120: * system identifiers specified in the document.</li>
0121: * <li><b>PUBLIC</b>
0122: * maps a public identifier to a system identifier.</li>
0123: * <li><b>SGMLDECL</b>
0124: * recognized and silently ignored. Not relevant for XML.</li>
0125: * <li><b>SYSTEM</b>
0126: * maps a system identifier to another system identifier.</li>
0127: * <li><b>URI</b>
0128: * maps a URI to another URI.</li>
0129: * </ul>
0130: *
0131: * <p>Note that BASE entries are treated as described by RFC2396. In
0132: * particular, this has the counter-intuitive property that after a BASE
0133: * entry identifing "http://example.com/a/b/c" as the base URI,
0134: * the relative URI "foo" is resolved to the absolute URI
0135: * "http://example.com/a/b/foo". You must provide the trailing slash if
0136: * you do not want the final component of the path to be discarded as a
0137: * filename would in a URI for a resource: "http://example.com/a/b/c/".
0138: * </p>
0139: *
0140: * <p>Note that subordinate catalogs (all catalogs except the first,
0141: * including CATALOG and DELEGATE* catalogs) are only loaded if and when
0142: * they are required.</p>
0143: *
0144: * <p>This class relies on classes which implement the CatalogReader
0145: * interface to actually load catalog files. This allows the catalog
0146: * semantics to be implemented for TR9401 text-based catalogs, XML
0147: * catalogs, or any number of other storage formats.</p>
0148: *
0149: * <p>Additional catalogs may also be loaded with the
0150: * {@link #parseCatalog} method.</p>
0151: * </dd>
0152: * </dl>
0153: *
0154: * <p><b>Change Log:</b></p>
0155: * <dl>
0156: * <dt>2.0</dt>
0157: * <dd><p>Rewrite to use CatalogReaders.</p></dd>
0158: * <dt>1.1</dt>
0159: * <dd><p>Allow quoted components in <tt>xml.catalog.files</tt>
0160: * so that URLs containing colons can be used on Unix.
0161: * The string passed to <tt>xml.catalog.files</tt> can now have the form:</p>
0162: * <pre>
0163: * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars'
0164: * </pre>
0165: * <p>(Where ":" is the separater character in this example.)</p>
0166: * <p>If an unquoted path contains an embedded double or single quote
0167: * character, no special processig is performed on that character. No
0168: * path can contain separater characters, double, and single quotes
0169: * simultaneously.</p>
0170: * <p>Fix bug in calculation of BASE entries: if
0171: * a catalog contains multiple BASE entries, each is relative to the preceding
0172: * base, not the default base URI of the catalog.</p>
0173: * </dd>
0174: * <dt>1.0.1</dt>
0175: * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs.
0176: * This bug caused an infinite loop where parsing would alternately process
0177: * two catalogs indefinitely.</p>
0178: * </dd>
0179: * </dl>
0180: *
0181: * @see CatalogReader
0182: * @see CatalogEntry
0183: *
0184: * @author Norman Walsh
0185: * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
0186: *
0187: * @version 1.0
0188: *
0189: * <p>Derived from public domain code originally published by Arbortext,
0190: * Inc.</p>
0191: */
0192: public class Catalog {
0193: /** The BASE Catalog Entry type. */
0194: public static final int BASE = CatalogEntry.addEntryType("BASE", 1);
0195:
0196: /** The CATALOG Catalog Entry type. */
0197: public static final int CATALOG = CatalogEntry.addEntryType(
0198: "CATALOG", 1);
0199:
0200: /** The DOCUMENT Catalog Entry type. */
0201: public static final int DOCUMENT = CatalogEntry.addEntryType(
0202: "DOCUMENT", 1);
0203:
0204: /** The OVERRIDE Catalog Entry type. */
0205: public static final int OVERRIDE = CatalogEntry.addEntryType(
0206: "OVERRIDE", 1);
0207:
0208: /** The SGMLDECL Catalog Entry type. */
0209: public static final int SGMLDECL = CatalogEntry.addEntryType(
0210: "SGMLDECL", 1);
0211:
0212: /** The DELEGATE_PUBLIC Catalog Entry type. */
0213: public static final int DELEGATE_PUBLIC = CatalogEntry
0214: .addEntryType("DELEGATE_PUBLIC", 2);
0215:
0216: /** The DELEGATE_SYSTEM Catalog Entry type. */
0217: public static final int DELEGATE_SYSTEM = CatalogEntry
0218: .addEntryType("DELEGATE_SYSTEM", 2);
0219:
0220: /** The DELEGATE_URI Catalog Entry type. */
0221: public static final int DELEGATE_URI = CatalogEntry.addEntryType(
0222: "DELEGATE_URI", 2);
0223:
0224: /** The DOCTYPE Catalog Entry type. */
0225: public static final int DOCTYPE = CatalogEntry.addEntryType(
0226: "DOCTYPE", 2);
0227:
0228: /** The DTDDECL Catalog Entry type. */
0229: public static final int DTDDECL = CatalogEntry.addEntryType(
0230: "DTDDECL", 2);
0231:
0232: /** The ENTITY Catalog Entry type. */
0233: public static final int ENTITY = CatalogEntry.addEntryType(
0234: "ENTITY", 2);
0235:
0236: /** The LINKTYPE Catalog Entry type. */
0237: public static final int LINKTYPE = CatalogEntry.addEntryType(
0238: "LINKTYPE", 2);
0239:
0240: /** The NOTATION Catalog Entry type. */
0241: public static final int NOTATION = CatalogEntry.addEntryType(
0242: "NOTATION", 2);
0243:
0244: /** The PUBLIC Catalog Entry type. */
0245: public static final int PUBLIC = CatalogEntry.addEntryType(
0246: "PUBLIC", 2);
0247:
0248: /** The SYSTEM Catalog Entry type. */
0249: public static final int SYSTEM = CatalogEntry.addEntryType(
0250: "SYSTEM", 2);
0251:
0252: /** The URI Catalog Entry type. */
0253: public static final int URI = CatalogEntry.addEntryType("URI", 2);
0254:
0255: /** The REWRITE_SYSTEM Catalog Entry type. */
0256: public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType(
0257: "REWRITE_SYSTEM", 2);
0258:
0259: /** The REWRITE_URI Catalog Entry type. */
0260: public static final int REWRITE_URI = CatalogEntry.addEntryType(
0261: "REWRITE_URI", 2);
0262: /** The SYSTEM_SUFFIX Catalog Entry type. */
0263: public static final int SYSTEM_SUFFIX = CatalogEntry.addEntryType(
0264: "SYSTEM_SUFFIX", 2);
0265: /** The URI_SUFFIX Catalog Entry type. */
0266: public static final int URI_SUFFIX = CatalogEntry.addEntryType(
0267: "URI_SUFFIX", 2);
0268:
0269: /**
0270: * The base URI for relative system identifiers in the catalog.
0271: * This may be changed by BASE entries in the catalog.
0272: */
0273: protected URL base;
0274:
0275: /** The base URI of the Catalog file currently being parsed. */
0276: protected URL catalogCwd;
0277:
0278: /** The catalog entries currently known to the system. */
0279: protected Vector catalogEntries = new Vector();
0280:
0281: /** The default initial override setting. */
0282: protected boolean default_override = true;
0283:
0284: /** The catalog manager in use for this instance. */
0285: protected CatalogManager catalogManager = CatalogManager
0286: .getStaticManager();
0287:
0288: /**
0289: * A vector of catalog files to be loaded.
0290: *
0291: * <p>This list is initially established by
0292: * <code>loadSystemCatalogs</code> when
0293: * it parses the system catalog list, but CATALOG entries may
0294: * contribute to it during the course of parsing.</p>
0295: *
0296: * @see #loadSystemCatalogs
0297: * @see #localCatalogFiles
0298: */
0299: protected Vector catalogFiles = new Vector();
0300:
0301: /**
0302: * A vector of catalog files constructed during processing of
0303: * CATALOG entries in the current catalog.
0304: *
0305: * <p>This two-level system is actually necessary to correctly implement
0306: * the semantics of the CATALOG entry. If one catalog file includes
0307: * another with a CATALOG entry, the included catalog logically
0308: * occurs <i>at the end</i> of the including catalog, and after any
0309: * preceding CATALOG entries. In other words, the CATALOG entry
0310: * cannot insert anything into the middle of a catalog file.</p>
0311: *
0312: * <p>When processing reaches the end of each catalog files, any
0313: * elements on this vector are added to the front of the
0314: * <code>catalogFiles</code> vector.</p>
0315: *
0316: * @see #catalogFiles
0317: */
0318: protected Vector localCatalogFiles = new Vector();
0319:
0320: /**
0321: * A vector of Catalogs.
0322: *
0323: * <p>The semantics of Catalog resolution are such that each
0324: * catalog is effectively a list of Catalogs (in other words,
0325: * a recursive list of Catalog instances).</p>
0326: *
0327: * <p>Catalogs that are processed as the result of CATALOG or
0328: * DELEGATE* entries are subordinate to the catalog that contained
0329: * them, but they may in turn have subordinate catalogs.</p>
0330: *
0331: * <p>Catalogs are only loaded when they are needed, so this vector
0332: * initially contains a list of Catalog filenames (URLs). If, during
0333: * processing, one of these catalogs has to be loaded, the resulting
0334: * Catalog object is placed in the vector, effectively caching it
0335: * for the next query.</p>
0336: */
0337: protected Vector catalogs = new Vector();
0338:
0339: /**
0340: * A vector of DELEGATE* Catalog entries constructed during
0341: * processing of the Catalog.
0342: *
0343: * <p>This two-level system has two purposes; first, it allows
0344: * us to sort the DELEGATE* entries by the length of the partial
0345: * public identifier so that a linear search encounters them in
0346: * the correct order and second, it puts them all at the end of
0347: * the Catalog.</p>
0348: *
0349: * <p>When processing reaches the end of each catalog file, any
0350: * elements on this vector are added to the end of the
0351: * <code>catalogEntries</code> vector. This assures that matching
0352: * PUBLIC keywords are encountered before DELEGATE* entries.</p>
0353: */
0354: protected Vector localDelegate = new Vector();
0355:
0356: /**
0357: * A hash of CatalogReaders.
0358: *
0359: * <p>This hash maps MIME types to elements in the readerArr
0360: * vector. This allows the Catalog to quickly locate the reader
0361: * for a particular MIME type.</p>
0362: */
0363: protected Hashtable readerMap = new Hashtable();
0364:
0365: /**
0366: * A vector of CatalogReaders.
0367: *
0368: * <p>This vector contains all of the readers in the order that they
0369: * were added. In the event that a catalog is read from a file, where
0370: * the MIME type is unknown, each reader is attempted in turn until
0371: * one succeeds.</p>
0372: */
0373: protected Vector readerArr = new Vector();
0374:
0375: /**
0376: * Constructs an empty Catalog.
0377: *
0378: * <p>The constructor interrogates the relevant system properties
0379: * using the default (static) CatalogManager
0380: * and initializes the catalog data structures.</p>
0381: */
0382: public Catalog() {
0383: // nop;
0384: }
0385:
0386: /**
0387: * Constructs an empty Catalog with a specific CatalogManager.
0388: *
0389: * <p>The constructor interrogates the relevant system properties
0390: * using the specified Catalog Manager
0391: * and initializes the catalog data structures.</p>
0392: */
0393: public Catalog(CatalogManager manager) {
0394: catalogManager = manager;
0395: }
0396:
0397: /**
0398: * Return the CatalogManager used by this catalog.
0399: *
0400: */
0401: public CatalogManager getCatalogManager() {
0402: return catalogManager;
0403: }
0404:
0405: /**
0406: * Establish the CatalogManager used by this catalog.
0407: *
0408: */
0409: public void setCatalogManager(CatalogManager manager) {
0410: catalogManager = manager;
0411: }
0412:
0413: /**
0414: * Setup readers.
0415: */
0416: public void setupReaders() {
0417: SAXParserFactory spf = SAXParserFactory.newInstance();
0418: spf.setNamespaceAware(true);
0419: spf.setValidating(false);
0420:
0421: SAXCatalogReader saxReader = new SAXCatalogReader(spf);
0422:
0423: saxReader
0424: .setCatalogParser(null, "XMLCatalog",
0425: "com.sun.org.apache.xml.internal.resolver.readers.XCatalogReader");
0426:
0427: saxReader
0428: .setCatalogParser(OASISXMLCatalogReader.namespaceName,
0429: "catalog",
0430: "com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader");
0431:
0432: addReader("application/xml", saxReader);
0433:
0434: TR9401CatalogReader textReader = new TR9401CatalogReader();
0435: addReader("text/plain", textReader);
0436: }
0437:
0438: /**
0439: * Add a new CatalogReader to the Catalog.
0440: *
0441: * <p>This method allows you to add a new CatalogReader to the
0442: * catalog. The reader will be associated with the specified mimeType.
0443: * You can only have one reader per mimeType.</p>
0444: *
0445: * <p>In the absence of a mimeType (e.g., when reading a catalog
0446: * directly from a file on the local system), the readers are attempted
0447: * in the order that you add them to the Catalog.</p>
0448: *
0449: * <p>Note that subordinate catalogs (created by CATALOG or
0450: * DELEGATE* entries) get a copy of the set of readers present in
0451: * the primary catalog when they are created. Readers added subsequently
0452: * will not be available. For this reason, it is best to add all
0453: * of the readers before the first call to parse a catalog.</p>
0454: *
0455: * @param mimeType The MIME type associated with this reader.
0456: * @param reader The CatalogReader to use.
0457: */
0458: public void addReader(String mimeType, CatalogReader reader) {
0459: if (readerMap.containsKey(mimeType)) {
0460: Integer pos = (Integer) readerMap.get(mimeType);
0461: readerArr.set(pos.intValue(), reader);
0462: } else {
0463: readerArr.add(reader);
0464: Integer pos = new Integer(readerArr.size() - 1);
0465: readerMap.put(mimeType, pos);
0466: }
0467: }
0468:
0469: /**
0470: * Copies the reader list from the current Catalog to a new Catalog.
0471: *
0472: * <p>This method is used internally when constructing a new catalog.
0473: * It copies the current reader associations over to the new catalog.
0474: * </p>
0475: *
0476: * @param newCatalog The new Catalog.
0477: */
0478: protected void copyReaders(Catalog newCatalog) {
0479: // Have to copy the readers in the right order...convert hash to arr
0480: Vector mapArr = new Vector(readerMap.size());
0481:
0482: // Pad the mapArr out to the right length
0483: for (int count = 0; count < readerMap.size(); count++) {
0484: mapArr.add(null);
0485: }
0486:
0487: Enumeration en = readerMap.keys();
0488: while (en.hasMoreElements()) {
0489: String mimeType = (String) en.nextElement();
0490: Integer pos = (Integer) readerMap.get(mimeType);
0491: mapArr.set(pos.intValue(), mimeType);
0492: }
0493:
0494: for (int count = 0; count < mapArr.size(); count++) {
0495: String mimeType = (String) mapArr.get(count);
0496: Integer pos = (Integer) readerMap.get(mimeType);
0497: newCatalog.addReader(mimeType, (CatalogReader) readerArr
0498: .get(pos.intValue()));
0499: }
0500: }
0501:
0502: /**
0503: * Create a new Catalog object.
0504: *
0505: * <p>This method constructs a new instance of the running Catalog
0506: * class (which might be a subtype of com.sun.org.apache.xml.internal.resolver.Catalog).
0507: * All new catalogs are managed by the same CatalogManager.
0508: * </p>
0509: *
0510: * <p>N.B. All Catalog subtypes should call newCatalog() to construct
0511: * a new Catalog. Do not simply use "new Subclass()" since that will
0512: * confuse future subclasses.</p>
0513: */
0514: protected Catalog newCatalog() {
0515: String catalogClass = this .getClass().getName();
0516:
0517: try {
0518: Catalog c = (Catalog) (Class.forName(catalogClass)
0519: .newInstance());
0520: c.setCatalogManager(catalogManager);
0521: copyReaders(c);
0522: return c;
0523: } catch (ClassNotFoundException cnfe) {
0524: catalogManager.debug.message(1,
0525: "Class Not Found Exception: " + catalogClass);
0526: } catch (IllegalAccessException iae) {
0527: catalogManager.debug.message(1,
0528: "Illegal Access Exception: " + catalogClass);
0529: } catch (InstantiationException ie) {
0530: catalogManager.debug.message(1, "Instantiation Exception: "
0531: + catalogClass);
0532: } catch (ClassCastException cce) {
0533: catalogManager.debug.message(1, "Class Cast Exception: "
0534: + catalogClass);
0535: } catch (Exception e) {
0536: catalogManager.debug.message(1, "Other Exception: "
0537: + catalogClass);
0538: }
0539:
0540: Catalog c = new Catalog();
0541: c.setCatalogManager(catalogManager);
0542: copyReaders(c);
0543: return c;
0544: }
0545:
0546: /**
0547: * Returns the current base URI.
0548: */
0549: public String getCurrentBase() {
0550: return base.toString();
0551: }
0552:
0553: /**
0554: * Returns the default override setting associated with this
0555: * catalog.
0556: *
0557: * <p>All catalog files loaded by this catalog will have the
0558: * initial override setting specified by this default.</p>
0559: */
0560: public String getDefaultOverride() {
0561: if (default_override) {
0562: return "yes";
0563: } else {
0564: return "no";
0565: }
0566: }
0567:
0568: /**
0569: * Load the system catalog files.
0570: *
0571: * <p>The method adds all of the
0572: * catalogs specified in the <tt>xml.catalog.files</tt> property
0573: * to the Catalog list.</p>
0574: *
0575: * @throws MalformedURLException One of the system catalogs is
0576: * identified with a filename that is not a valid URL.
0577: * @throws IOException One of the system catalogs cannot be read.
0578: */
0579: public void loadSystemCatalogs() throws MalformedURLException,
0580: IOException {
0581:
0582: Vector catalogs = catalogManager.getCatalogFiles();
0583: if (catalogs != null) {
0584: for (int count = 0; count < catalogs.size(); count++) {
0585: catalogFiles.addElement(catalogs.elementAt(count));
0586: }
0587: }
0588:
0589: if (catalogFiles.size() > 0) {
0590: // This is a little odd. The parseCatalog() method expects
0591: // a filename, but it adds that name to the end of the
0592: // catalogFiles vector, and then processes that vector.
0593: // This allows the system to handle CATALOG entries
0594: // correctly.
0595: //
0596: // In this init case, we take the last element off the
0597: // catalogFiles vector and pass it to parseCatalog. This
0598: // will "do the right thing" in the init case, and allow
0599: // parseCatalog() to do the right thing in the non-init
0600: // case. Honest.
0601: //
0602: String catfile = (String) catalogFiles.lastElement();
0603: catalogFiles.removeElement(catfile);
0604: parseCatalog(catfile);
0605: }
0606: }
0607:
0608: /**
0609: * Parse a catalog file, augmenting internal data structures.
0610: *
0611: * @param fileName The filename of the catalog file to process
0612: *
0613: * @throws MalformedURLException The fileName cannot be turned into
0614: * a valid URL.
0615: * @throws IOException Error reading catalog file.
0616: */
0617: public synchronized void parseCatalog(String fileName)
0618: throws MalformedURLException, IOException {
0619:
0620: default_override = catalogManager.getPreferPublic();
0621: catalogManager.debug.message(4, "Parse catalog: " + fileName);
0622:
0623: // Put the file into the list of catalogs to process...
0624: // In all cases except the case when initCatalog() is the
0625: // caller, this will be the only catalog initially in the list...
0626: catalogFiles.addElement(fileName);
0627:
0628: // Now process all the pending catalogs...
0629: parsePendingCatalogs();
0630: }
0631:
0632: /**
0633: * Parse a catalog file, augmenting internal data structures.
0634: *
0635: * <p>Catalogs retrieved over the net may have an associated MIME type.
0636: * The MIME type can be used to select an appropriate reader.</p>
0637: *
0638: * @param mimeType The MIME type of the catalog file.
0639: * @param is The InputStream from which the catalog should be read
0640: *
0641: * @throws CatalogException Failed to load catalog
0642: * mimeType.
0643: * @throws IOException Error reading catalog file.
0644: */
0645: public synchronized void parseCatalog(String mimeType,
0646: InputStream is) throws IOException, CatalogException {
0647:
0648: default_override = catalogManager.getPreferPublic();
0649: catalogManager.debug.message(4, "Parse " + mimeType
0650: + " catalog on input stream");
0651:
0652: CatalogReader reader = null;
0653:
0654: if (readerMap.containsKey(mimeType)) {
0655: int arrayPos = ((Integer) readerMap.get(mimeType))
0656: .intValue();
0657: reader = (CatalogReader) readerArr.get(arrayPos);
0658: }
0659:
0660: if (reader == null) {
0661: String msg = "No CatalogReader for MIME type: " + mimeType;
0662: catalogManager.debug.message(2, msg);
0663: throw new CatalogException(CatalogException.UNPARSEABLE,
0664: msg);
0665: }
0666:
0667: reader.readCatalog(this , is);
0668:
0669: // Now process all the pending catalogs...
0670: parsePendingCatalogs();
0671: }
0672:
0673: /**
0674: * Parse a catalog document, augmenting internal data structures.
0675: *
0676: * <p>This method supports catalog files stored in jar files: e.g.,
0677: * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI
0678: * doesn't survive transmogrification through the URI processing that
0679: * the parseCatalog(String) performs and passing it as an input stream
0680: * doesn't set the base URI appropriately.</p>
0681: *
0682: * <p>Written by Stefan Wachter (2002-09-26)</p>
0683: *
0684: * @param aUrl The URL of the catalog document to process
0685: *
0686: * @throws IOException Error reading catalog file.
0687: */
0688: public synchronized void parseCatalog(URL aUrl) throws IOException {
0689: catalogCwd = aUrl;
0690: base = aUrl;
0691:
0692: default_override = catalogManager.getPreferPublic();
0693: catalogManager.debug.message(4, "Parse catalog: "
0694: + aUrl.toString());
0695:
0696: DataInputStream inStream = null;
0697: boolean parsed = false;
0698:
0699: for (int count = 0; !parsed && count < readerArr.size(); count++) {
0700: CatalogReader reader = (CatalogReader) readerArr.get(count);
0701:
0702: try {
0703: inStream = new DataInputStream(aUrl.openStream());
0704: } catch (FileNotFoundException fnfe) {
0705: // No catalog; give up!
0706: break;
0707: }
0708:
0709: try {
0710: reader.readCatalog(this , inStream);
0711: parsed = true;
0712: } catch (CatalogException ce) {
0713: if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
0714: // give up!
0715: break;
0716: } else {
0717: // try again!
0718: }
0719: }
0720:
0721: try {
0722: inStream.close();
0723: } catch (IOException e) {
0724: //nop
0725: }
0726: }
0727:
0728: if (parsed)
0729: parsePendingCatalogs();
0730: }
0731:
0732: /**
0733: * Parse all of the pending catalogs.
0734: *
0735: * <p>Catalogs may refer to other catalogs, this method parses
0736: * all of the currently pending catalog files.</p>
0737: */
0738: protected synchronized void parsePendingCatalogs()
0739: throws MalformedURLException, IOException {
0740:
0741: if (!localCatalogFiles.isEmpty()) {
0742: // Move all the localCatalogFiles into the front of
0743: // the catalogFiles queue
0744: Vector newQueue = new Vector();
0745: Enumeration q = localCatalogFiles.elements();
0746: while (q.hasMoreElements()) {
0747: newQueue.addElement(q.nextElement());
0748: }
0749:
0750: // Put the rest of the catalogs on the end of the new list
0751: for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
0752: String catfile = (String) catalogFiles
0753: .elementAt(curCat);
0754: newQueue.addElement(catfile);
0755: }
0756:
0757: catalogFiles = newQueue;
0758: localCatalogFiles.clear();
0759: }
0760:
0761: // Suppose there are no catalog files to process, but the
0762: // single catalog already parsed included some delegate
0763: // entries? Make sure they don't get lost.
0764: if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) {
0765: Enumeration e = localDelegate.elements();
0766: while (e.hasMoreElements()) {
0767: catalogEntries.addElement(e.nextElement());
0768: }
0769: localDelegate.clear();
0770: }
0771:
0772: // Now process all the files on the catalogFiles vector. This
0773: // vector can grow during processing if CATALOG entries are
0774: // encountered in the catalog
0775: while (!catalogFiles.isEmpty()) {
0776: String catfile = (String) catalogFiles.elementAt(0);
0777: try {
0778: catalogFiles.remove(0);
0779: } catch (ArrayIndexOutOfBoundsException e) {
0780: // can't happen
0781: }
0782:
0783: if (catalogEntries.size() == 0 && catalogs.size() == 0) {
0784: // We haven't parsed any catalogs yet, let this
0785: // catalog be the first...
0786: try {
0787: parseCatalogFile(catfile);
0788: } catch (CatalogException ce) {
0789: System.out.println("FIXME: " + ce.toString());
0790: }
0791: } else {
0792: // This is a subordinate catalog. We save its name,
0793: // but don't bother to load it unless it's necessary.
0794: catalogs.addElement(catfile);
0795: }
0796:
0797: if (!localCatalogFiles.isEmpty()) {
0798: // Move all the localCatalogFiles into the front of
0799: // the catalogFiles queue
0800: Vector newQueue = new Vector();
0801: Enumeration q = localCatalogFiles.elements();
0802: while (q.hasMoreElements()) {
0803: newQueue.addElement(q.nextElement());
0804: }
0805:
0806: // Put the rest of the catalogs on the end of the new list
0807: for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
0808: catfile = (String) catalogFiles.elementAt(curCat);
0809: newQueue.addElement(catfile);
0810: }
0811:
0812: catalogFiles = newQueue;
0813: localCatalogFiles.clear();
0814: }
0815:
0816: if (!localDelegate.isEmpty()) {
0817: Enumeration e = localDelegate.elements();
0818: while (e.hasMoreElements()) {
0819: catalogEntries.addElement(e.nextElement());
0820: }
0821: localDelegate.clear();
0822: }
0823: }
0824:
0825: // We've parsed them all, reinit the vector...
0826: catalogFiles.clear();
0827: }
0828:
0829: /**
0830: * Parse a single catalog file, augmenting internal data structures.
0831: *
0832: * @param fileName The filename of the catalog file to process
0833: *
0834: * @throws MalformedURLException The fileName cannot be turned into
0835: * a valid URL.
0836: * @throws IOException Error reading catalog file.
0837: */
0838: protected synchronized void parseCatalogFile(String fileName)
0839: throws MalformedURLException, IOException, CatalogException {
0840:
0841: CatalogEntry entry;
0842:
0843: // The base-base is the cwd. If the catalog file is specified
0844: // with a relative path, this assures that it gets resolved
0845: // properly...
0846: try {
0847: // tack on a basename because URLs point to files not dirs
0848: catalogCwd = FileURL.makeURL("basename");
0849: } catch (MalformedURLException e) {
0850: String userdir = System.getProperty("user.dir");
0851: userdir.replace('\\', '/');
0852: catalogManager.debug.message(1, "Malformed URL on cwd",
0853: userdir);
0854: catalogCwd = null;
0855: }
0856:
0857: // The initial base URI is the location of the catalog file
0858: try {
0859: base = new URL(catalogCwd, fixSlashes(fileName));
0860: } catch (MalformedURLException e) {
0861: try {
0862: base = new URL("file:" + fixSlashes(fileName));
0863: } catch (MalformedURLException e2) {
0864: catalogManager.debug.message(1,
0865: "Malformed URL on catalog filename",
0866: fixSlashes(fileName));
0867: base = null;
0868: }
0869: }
0870:
0871: catalogManager.debug.message(2, "Loading catalog", fileName);
0872: catalogManager.debug
0873: .message(4, "Default BASE", base.toString());
0874:
0875: fileName = base.toString();
0876:
0877: DataInputStream inStream = null;
0878: boolean parsed = false;
0879: boolean notFound = false;
0880:
0881: for (int count = 0; !parsed && count < readerArr.size(); count++) {
0882: CatalogReader reader = (CatalogReader) readerArr.get(count);
0883:
0884: try {
0885: notFound = false;
0886: inStream = new DataInputStream(base.openStream());
0887: } catch (FileNotFoundException fnfe) {
0888: // No catalog; give up!
0889: notFound = true;
0890: break;
0891: }
0892:
0893: try {
0894: reader.readCatalog(this , inStream);
0895: parsed = true;
0896: } catch (CatalogException ce) {
0897: if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
0898: // give up!
0899: break;
0900: } else {
0901: // try again!
0902: }
0903: }
0904:
0905: try {
0906: inStream.close();
0907: } catch (IOException e) {
0908: //nop
0909: }
0910: }
0911:
0912: if (!parsed) {
0913: if (notFound) {
0914: catalogManager.debug.message(3,
0915: "Catalog does not exist", fileName);
0916: } else {
0917: catalogManager.debug.message(1,
0918: "Failed to parse catalog", fileName);
0919: }
0920: }
0921: }
0922:
0923: /**
0924: * Cleanup and process a Catalog entry.
0925: *
0926: * <p>This method processes each Catalog entry, changing mapped
0927: * relative system identifiers into absolute ones (based on the current
0928: * base URI), and maintaining other information about the current
0929: * catalog.</p>
0930: *
0931: * @param entry The CatalogEntry to process.
0932: */
0933: public void addEntry(CatalogEntry entry) {
0934: int type = entry.getEntryType();
0935:
0936: if (type == BASE) {
0937: String value = entry.getEntryArg(0);
0938: URL newbase = null;
0939:
0940: if (base == null) {
0941: catalogManager.debug.message(5, "BASE CUR", "null");
0942: } else {
0943: catalogManager.debug.message(5, "BASE CUR", base
0944: .toString());
0945: }
0946: catalogManager.debug.message(4, "BASE STR", value);
0947:
0948: try {
0949: value = fixSlashes(value);
0950: newbase = new URL(base, value);
0951: } catch (MalformedURLException e) {
0952: try {
0953: newbase = new URL("file:" + value);
0954: } catch (MalformedURLException e2) {
0955: catalogManager.debug.message(1,
0956: "Malformed URL on base", value);
0957: newbase = null;
0958: }
0959: }
0960:
0961: if (newbase != null) {
0962: base = newbase;
0963: }
0964:
0965: catalogManager.debug
0966: .message(5, "BASE NEW", base.toString());
0967: } else if (type == CATALOG) {
0968: String fsi = makeAbsolute(entry.getEntryArg(0));
0969:
0970: catalogManager.debug.message(4, "CATALOG", fsi);
0971:
0972: localCatalogFiles.addElement(fsi);
0973: } else if (type == PUBLIC) {
0974: String publicid = PublicId.normalize(entry.getEntryArg(0));
0975: String systemid = makeAbsolute(normalizeURI(entry
0976: .getEntryArg(1)));
0977:
0978: entry.setEntryArg(0, publicid);
0979: entry.setEntryArg(1, systemid);
0980:
0981: catalogManager.debug.message(4, "PUBLIC", publicid,
0982: systemid);
0983:
0984: catalogEntries.addElement(entry);
0985: } else if (type == SYSTEM) {
0986: String systemid = normalizeURI(entry.getEntryArg(0));
0987: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
0988:
0989: entry.setEntryArg(1, fsi);
0990:
0991: catalogManager.debug.message(4, "SYSTEM", systemid, fsi);
0992:
0993: catalogEntries.addElement(entry);
0994: } else if (type == URI) {
0995: String uri = normalizeURI(entry.getEntryArg(0));
0996: String altURI = makeAbsolute(normalizeURI(entry
0997: .getEntryArg(1)));
0998:
0999: entry.setEntryArg(1, altURI);
1000:
1001: catalogManager.debug.message(4, "URI", uri, altURI);
1002:
1003: catalogEntries.addElement(entry);
1004: } else if (type == DOCUMENT) {
1005: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
1006: entry.setEntryArg(0, fsi);
1007:
1008: catalogManager.debug.message(4, "DOCUMENT", fsi);
1009:
1010: catalogEntries.addElement(entry);
1011: } else if (type == OVERRIDE) {
1012: catalogManager.debug.message(4, "OVERRIDE", entry
1013: .getEntryArg(0));
1014:
1015: catalogEntries.addElement(entry);
1016: } else if (type == SGMLDECL) {
1017: // meaningless in XML
1018: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
1019: entry.setEntryArg(0, fsi);
1020:
1021: catalogManager.debug.message(4, "SGMLDECL", fsi);
1022:
1023: catalogEntries.addElement(entry);
1024: } else if (type == DELEGATE_PUBLIC) {
1025: String ppi = PublicId.normalize(entry.getEntryArg(0));
1026: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1027:
1028: entry.setEntryArg(0, ppi);
1029: entry.setEntryArg(1, fsi);
1030:
1031: catalogManager.debug
1032: .message(4, "DELEGATE_PUBLIC", ppi, fsi);
1033:
1034: addDelegate(entry);
1035: } else if (type == DELEGATE_SYSTEM) {
1036: String psi = normalizeURI(entry.getEntryArg(0));
1037: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1038:
1039: entry.setEntryArg(0, psi);
1040: entry.setEntryArg(1, fsi);
1041:
1042: catalogManager.debug
1043: .message(4, "DELEGATE_SYSTEM", psi, fsi);
1044:
1045: addDelegate(entry);
1046: } else if (type == DELEGATE_URI) {
1047: String pui = normalizeURI(entry.getEntryArg(0));
1048: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1049:
1050: entry.setEntryArg(0, pui);
1051: entry.setEntryArg(1, fsi);
1052:
1053: catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi);
1054:
1055: addDelegate(entry);
1056: } else if (type == REWRITE_SYSTEM) {
1057: String psi = normalizeURI(entry.getEntryArg(0));
1058: String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1059:
1060: entry.setEntryArg(0, psi);
1061: entry.setEntryArg(1, rpx);
1062:
1063: catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx);
1064:
1065: catalogEntries.addElement(entry);
1066: } else if (type == REWRITE_URI) {
1067: String pui = normalizeURI(entry.getEntryArg(0));
1068: String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1069:
1070: entry.setEntryArg(0, pui);
1071: entry.setEntryArg(1, upx);
1072:
1073: catalogManager.debug.message(4, "REWRITE_URI", pui, upx);
1074:
1075: catalogEntries.addElement(entry);
1076: } else if (type == SYSTEM_SUFFIX) {
1077: String pui = normalizeURI(entry.getEntryArg(0));
1078: String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1079:
1080: entry.setEntryArg(0, pui);
1081: entry.setEntryArg(1, upx);
1082:
1083: catalogManager.debug.message(4, "SYSTEM_SUFFIX", pui, upx);
1084:
1085: catalogEntries.addElement(entry);
1086: } else if (type == URI_SUFFIX) {
1087: String pui = normalizeURI(entry.getEntryArg(0));
1088: String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1089:
1090: entry.setEntryArg(0, pui);
1091: entry.setEntryArg(1, upx);
1092:
1093: catalogManager.debug.message(4, "URI_SUFFIX", pui, upx);
1094:
1095: catalogEntries.addElement(entry);
1096: } else if (type == DOCTYPE) {
1097: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1098: entry.setEntryArg(1, fsi);
1099:
1100: catalogManager.debug.message(4, "DOCTYPE", entry
1101: .getEntryArg(0), fsi);
1102:
1103: catalogEntries.addElement(entry);
1104: } else if (type == DTDDECL) {
1105: // meaningless in XML
1106: String fpi = PublicId.normalize(entry.getEntryArg(0));
1107: entry.setEntryArg(0, fpi);
1108: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1109: entry.setEntryArg(1, fsi);
1110:
1111: catalogManager.debug.message(4, "DTDDECL", fpi, fsi);
1112:
1113: catalogEntries.addElement(entry);
1114: } else if (type == ENTITY) {
1115: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1116: entry.setEntryArg(1, fsi);
1117:
1118: catalogManager.debug.message(4, "ENTITY", entry
1119: .getEntryArg(0), fsi);
1120:
1121: catalogEntries.addElement(entry);
1122: } else if (type == LINKTYPE) {
1123: // meaningless in XML
1124: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1125: entry.setEntryArg(1, fsi);
1126:
1127: catalogManager.debug.message(4, "LINKTYPE", entry
1128: .getEntryArg(0), fsi);
1129:
1130: catalogEntries.addElement(entry);
1131: } else if (type == NOTATION) {
1132: String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
1133: entry.setEntryArg(1, fsi);
1134:
1135: catalogManager.debug.message(4, "NOTATION", entry
1136: .getEntryArg(0), fsi);
1137:
1138: catalogEntries.addElement(entry);
1139: } else {
1140: catalogEntries.addElement(entry);
1141: }
1142: }
1143:
1144: /**
1145: * Handle unknown CatalogEntry types.
1146: *
1147: * <p>This method exists to allow subclasses to deal with unknown
1148: * entry types.</p>
1149: */
1150: public void unknownEntry(Vector strings) {
1151: if (strings != null && strings.size() > 0) {
1152: String keyword = (String) strings.elementAt(0);
1153: catalogManager.debug.message(2,
1154: "Unrecognized token parsing catalog", keyword);
1155: }
1156: }
1157:
1158: /**
1159: * Parse all subordinate catalogs.
1160: *
1161: * <p>This method recursively parses all of the subordinate catalogs.
1162: * If this method does not throw an exception, you can be confident that
1163: * no subsequent call to any resolve*() method will either, with two
1164: * possible exceptions:</p>
1165: *
1166: * <ol>
1167: * <li><p>Delegated catalogs are re-parsed each time they are needed
1168: * (because a variable list of them may be needed in each case,
1169: * depending on the length of the matching partial public identifier).</p>
1170: * <p>But they are parsed by this method, so as long as they don't
1171: * change or disappear while the program is running, they shouldn't
1172: * generate errors later if they don't generate errors now.</p>
1173: * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
1174: * won't be loaded until they are needed or until you call
1175: * <code>parseAllCatalogs</code> again.</p>
1176: * </ol>
1177: *
1178: * <p>On the other hand, if you don't call this method, you may
1179: * successfully parse documents without having to load all possible
1180: * catalogs.</p>
1181: *
1182: * @throws MalformedURLException The filename (URL) for a
1183: * subordinate or delegated catalog is not a valid URL.
1184: * @throws IOException Error reading some subordinate or delegated
1185: * catalog file.
1186: */
1187: public void parseAllCatalogs() throws MalformedURLException,
1188: IOException {
1189:
1190: // Parse all the subordinate catalogs
1191: for (int catPos = 0; catPos < catalogs.size(); catPos++) {
1192: Catalog c = null;
1193:
1194: try {
1195: c = (Catalog) catalogs.elementAt(catPos);
1196: } catch (ClassCastException e) {
1197: String catfile = (String) catalogs.elementAt(catPos);
1198: c = newCatalog();
1199:
1200: c.parseCatalog(catfile);
1201: catalogs.setElementAt(c, catPos);
1202: c.parseAllCatalogs();
1203: }
1204: }
1205:
1206: // Parse all the DELEGATE catalogs
1207: Enumeration en = catalogEntries.elements();
1208: while (en.hasMoreElements()) {
1209: CatalogEntry e = (CatalogEntry) en.nextElement();
1210: if (e.getEntryType() == DELEGATE_PUBLIC
1211: || e.getEntryType() == DELEGATE_SYSTEM
1212: || e.getEntryType() == DELEGATE_URI) {
1213: Catalog dcat = newCatalog();
1214: dcat.parseCatalog(e.getEntryArg(1));
1215: }
1216: }
1217: }
1218:
1219: /**
1220: * Return the applicable DOCTYPE system identifier.
1221: *
1222: * @param entityName The name of the entity (element) for which
1223: * a doctype is required.
1224: * @param publicId The nominal public identifier for the doctype
1225: * (as provided in the source document).
1226: * @param systemId The nominal system identifier for the doctype
1227: * (as provided in the source document).
1228: *
1229: * @return The system identifier to use for the doctype.
1230: *
1231: * @throws MalformedURLException The formal system identifier of a
1232: * subordinate catalog cannot be turned into a valid URL.
1233: * @throws IOException Error reading subordinate catalog file.
1234: */
1235: public String resolveDoctype(String entityName, String publicId,
1236: String systemId) throws MalformedURLException, IOException {
1237: String resolved = null;
1238:
1239: catalogManager.debug.message(3, "resolveDoctype(" + entityName
1240: + "," + publicId + "," + systemId + ")");
1241:
1242: systemId = normalizeURI(systemId);
1243:
1244: if (publicId != null && publicId.startsWith("urn:publicid:")) {
1245: publicId = PublicId.decodeURN(publicId);
1246: }
1247:
1248: if (systemId != null && systemId.startsWith("urn:publicid:")) {
1249: systemId = PublicId.decodeURN(systemId);
1250: if (publicId != null && !publicId.equals(systemId)) {
1251: catalogManager.debug
1252: .message(
1253: 1,
1254: "urn:publicid: system identifier differs from public identifier; using public identifier");
1255: systemId = null;
1256: } else {
1257: publicId = systemId;
1258: systemId = null;
1259: }
1260: }
1261:
1262: if (systemId != null) {
1263: // If there's a SYSTEM entry in this catalog, use it
1264: resolved = resolveLocalSystem(systemId);
1265: if (resolved != null) {
1266: return resolved;
1267: }
1268: }
1269:
1270: if (publicId != null) {
1271: // If there's a PUBLIC entry in this catalog, use it
1272: resolved = resolveLocalPublic(DOCTYPE, entityName,
1273: publicId, systemId);
1274: if (resolved != null) {
1275: return resolved;
1276: }
1277: }
1278:
1279: // If there's a DOCTYPE entry in this catalog, use it
1280: boolean over = default_override;
1281: Enumeration en = catalogEntries.elements();
1282: while (en.hasMoreElements()) {
1283: CatalogEntry e = (CatalogEntry) en.nextElement();
1284: if (e.getEntryType() == OVERRIDE) {
1285: over = e.getEntryArg(0).equalsIgnoreCase("YES");
1286: continue;
1287: }
1288:
1289: if (e.getEntryType() == DOCTYPE
1290: && e.getEntryArg(0).equals(entityName)) {
1291: if (over || systemId == null) {
1292: return e.getEntryArg(1);
1293: }
1294: }
1295: }
1296:
1297: // Otherwise, look in the subordinate catalogs
1298: return resolveSubordinateCatalogs(DOCTYPE, entityName,
1299: publicId, systemId);
1300: }
1301:
1302: /**
1303: * Return the applicable DOCUMENT entry.
1304: *
1305: * @return The system identifier to use for the doctype.
1306: *
1307: * @throws MalformedURLException The formal system identifier of a
1308: * subordinate catalog cannot be turned into a valid URL.
1309: * @throws IOException Error reading subordinate catalog file.
1310: */
1311: public String resolveDocument() throws MalformedURLException,
1312: IOException {
1313: // If there's a DOCUMENT entry, return it
1314:
1315: catalogManager.debug.message(3, "resolveDocument");
1316:
1317: Enumeration en = catalogEntries.elements();
1318: while (en.hasMoreElements()) {
1319: CatalogEntry e = (CatalogEntry) en.nextElement();
1320: if (e.getEntryType() == DOCUMENT) {
1321: return e.getEntryArg(0);
1322: }
1323: }
1324:
1325: return resolveSubordinateCatalogs(DOCUMENT, null, null, null);
1326: }
1327:
1328: /**
1329: * Return the applicable ENTITY system identifier.
1330: *
1331: * @param entityName The name of the entity for which
1332: * a system identifier is required.
1333: * @param publicId The nominal public identifier for the entity
1334: * (as provided in the source document).
1335: * @param systemId The nominal system identifier for the entity
1336: * (as provided in the source document).
1337: *
1338: * @return The system identifier to use for the entity.
1339: *
1340: * @throws MalformedURLException The formal system identifier of a
1341: * subordinate catalog cannot be turned into a valid URL.
1342: * @throws IOException Error reading subordinate catalog file.
1343: */
1344: public String resolveEntity(String entityName, String publicId,
1345: String systemId) throws MalformedURLException, IOException {
1346: String resolved = null;
1347:
1348: catalogManager.debug.message(3, "resolveEntity(" + entityName
1349: + "," + publicId + "," + systemId + ")");
1350:
1351: systemId = normalizeURI(systemId);
1352:
1353: if (publicId != null && publicId.startsWith("urn:publicid:")) {
1354: publicId = PublicId.decodeURN(publicId);
1355: }
1356:
1357: if (systemId != null && systemId.startsWith("urn:publicid:")) {
1358: systemId = PublicId.decodeURN(systemId);
1359: if (publicId != null && !publicId.equals(systemId)) {
1360: catalogManager.debug
1361: .message(
1362: 1,
1363: "urn:publicid: system identifier differs from public identifier; using public identifier");
1364: systemId = null;
1365: } else {
1366: publicId = systemId;
1367: systemId = null;
1368: }
1369: }
1370:
1371: if (systemId != null) {
1372: // If there's a SYSTEM entry in this catalog, use it
1373: resolved = resolveLocalSystem(systemId);
1374: if (resolved != null) {
1375: return resolved;
1376: }
1377: }
1378:
1379: if (publicId != null) {
1380: // If there's a PUBLIC entry in this catalog, use it
1381: resolved = resolveLocalPublic(ENTITY, entityName, publicId,
1382: systemId);
1383: if (resolved != null) {
1384: return resolved;
1385: }
1386: }
1387:
1388: // If there's a ENTITY entry in this catalog, use it
1389: boolean over = default_override;
1390: Enumeration en = catalogEntries.elements();
1391: while (en.hasMoreElements()) {
1392: CatalogEntry e = (CatalogEntry) en.nextElement();
1393: if (e.getEntryType() == OVERRIDE) {
1394: over = e.getEntryArg(0).equalsIgnoreCase("YES");
1395: continue;
1396: }
1397:
1398: if (e.getEntryType() == ENTITY
1399: && e.getEntryArg(0).equals(entityName)) {
1400: if (over || systemId == null) {
1401: return e.getEntryArg(1);
1402: }
1403: }
1404: }
1405:
1406: // Otherwise, look in the subordinate catalogs
1407: return resolveSubordinateCatalogs(ENTITY, entityName, publicId,
1408: systemId);
1409: }
1410:
1411: /**
1412: * Return the applicable NOTATION system identifier.
1413: *
1414: * @param notationName The name of the notation for which
1415: * a doctype is required.
1416: * @param publicId The nominal public identifier for the notation
1417: * (as provided in the source document).
1418: * @param systemId The nominal system identifier for the notation
1419: * (as provided in the source document).
1420: *
1421: * @return The system identifier to use for the notation.
1422: *
1423: * @throws MalformedURLException The formal system identifier of a
1424: * subordinate catalog cannot be turned into a valid URL.
1425: * @throws IOException Error reading subordinate catalog file.
1426: */
1427: public String resolveNotation(String notationName, String publicId,
1428: String systemId) throws MalformedURLException, IOException {
1429: String resolved = null;
1430:
1431: catalogManager.debug.message(3, "resolveNotation("
1432: + notationName + "," + publicId + "," + systemId + ")");
1433:
1434: systemId = normalizeURI(systemId);
1435:
1436: if (publicId != null && publicId.startsWith("urn:publicid:")) {
1437: publicId = PublicId.decodeURN(publicId);
1438: }
1439:
1440: if (systemId != null && systemId.startsWith("urn:publicid:")) {
1441: systemId = PublicId.decodeURN(systemId);
1442: if (publicId != null && !publicId.equals(systemId)) {
1443: catalogManager.debug
1444: .message(
1445: 1,
1446: "urn:publicid: system identifier differs from public identifier; using public identifier");
1447: systemId = null;
1448: } else {
1449: publicId = systemId;
1450: systemId = null;
1451: }
1452: }
1453:
1454: if (systemId != null) {
1455: // If there's a SYSTEM entry in this catalog, use it
1456: resolved = resolveLocalSystem(systemId);
1457: if (resolved != null) {
1458: return resolved;
1459: }
1460: }
1461:
1462: if (publicId != null) {
1463: // If there's a PUBLIC entry in this catalog, use it
1464: resolved = resolveLocalPublic(NOTATION, notationName,
1465: publicId, systemId);
1466: if (resolved != null) {
1467: return resolved;
1468: }
1469: }
1470:
1471: // If there's a NOTATION entry in this catalog, use it
1472: boolean over = default_override;
1473: Enumeration en = catalogEntries.elements();
1474: while (en.hasMoreElements()) {
1475: CatalogEntry e = (CatalogEntry) en.nextElement();
1476: if (e.getEntryType() == OVERRIDE) {
1477: over = e.getEntryArg(0).equalsIgnoreCase("YES");
1478: continue;
1479: }
1480:
1481: if (e.getEntryType() == NOTATION
1482: && e.getEntryArg(0).equals(notationName)) {
1483: if (over || systemId == null) {
1484: return e.getEntryArg(1);
1485: }
1486: }
1487: }
1488:
1489: // Otherwise, look in the subordinate catalogs
1490: return resolveSubordinateCatalogs(NOTATION, notationName,
1491: publicId, systemId);
1492: }
1493:
1494: /**
1495: * Return the applicable PUBLIC or SYSTEM identifier.
1496: *
1497: * <p>This method searches the Catalog and returns the system
1498: * identifier specified for the given system or
1499: * public identifiers. If
1500: * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1501: * null is returned.</p>
1502: *
1503: * @param publicId The public identifier to locate in the catalog.
1504: * Public identifiers are normalized before comparison.
1505: * @param systemId The nominal system identifier for the entity
1506: * in question (as provided in the source document).
1507: *
1508: * @throws MalformedURLException The formal system identifier of a
1509: * subordinate catalog cannot be turned into a valid URL.
1510: * @throws IOException Error reading subordinate catalog file.
1511: *
1512: * @return The system identifier to use.
1513: * Note that the nominal system identifier is not returned if a
1514: * match is not found in the catalog, instead null is returned
1515: * to indicate that no match was found.
1516: */
1517: public String resolvePublic(String publicId, String systemId)
1518: throws MalformedURLException, IOException {
1519:
1520: catalogManager.debug.message(3, "resolvePublic(" + publicId
1521: + "," + systemId + ")");
1522:
1523: systemId = normalizeURI(systemId);
1524:
1525: if (publicId != null && publicId.startsWith("urn:publicid:")) {
1526: publicId = PublicId.decodeURN(publicId);
1527: }
1528:
1529: if (systemId != null && systemId.startsWith("urn:publicid:")) {
1530: systemId = PublicId.decodeURN(systemId);
1531: if (publicId != null && !publicId.equals(systemId)) {
1532: catalogManager.debug
1533: .message(
1534: 1,
1535: "urn:publicid: system identifier differs from public identifier; using public identifier");
1536: systemId = null;
1537: } else {
1538: publicId = systemId;
1539: systemId = null;
1540: }
1541: }
1542:
1543: // If there's a SYSTEM entry in this catalog, use it
1544: if (systemId != null) {
1545: String resolved = resolveLocalSystem(systemId);
1546: if (resolved != null) {
1547: return resolved;
1548: }
1549: }
1550:
1551: // If there's a PUBLIC entry in this catalog, use it
1552: String resolved = resolveLocalPublic(PUBLIC, null, publicId,
1553: systemId);
1554: if (resolved != null) {
1555: return resolved;
1556: }
1557:
1558: // Otherwise, look in the subordinate catalogs
1559: return resolveSubordinateCatalogs(PUBLIC, null, publicId,
1560: systemId);
1561: }
1562:
1563: /**
1564: * Return the applicable PUBLIC or SYSTEM identifier.
1565: *
1566: * <p>This method searches the Catalog and returns the system
1567: * identifier specified for the given system or public identifiers.
1568: * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
1569: * delegated Catalogs are interrogated.</p>
1570: *
1571: * <p>There are four possible cases:</p>
1572: *
1573: * <ul>
1574: * <li>If the system identifier provided matches a SYSTEM entry
1575: * in the current catalog, the SYSTEM entry is returned.
1576: * <li>If the system identifier is not null, the PUBLIC entries
1577: * that were encountered when OVERRIDE YES was in effect are
1578: * interrogated and the first matching entry is returned.</li>
1579: * <li>If the system identifier is null, then all of the PUBLIC
1580: * entries are interrogated and the first matching entry
1581: * is returned. This may not be the same as the preceding case, if
1582: * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
1583: * XML, the only place where a public identifier may occur without
1584: * a system identifier is in a notation declaration.</li>
1585: * <li>Finally, if the public identifier matches one of the partial
1586: * public identifiers specified in a DELEGATE* entry in
1587: * the Catalog, the delegated catalog is interrogated. The first
1588: * time that the delegated catalog is required, it will be
1589: * retrieved and parsed. It is subsequently cached.
1590: * </li>
1591: * </ul>
1592: *
1593: * @param entityType The CatalogEntry type for which this query is
1594: * being conducted. This is necessary in order to do the approprate
1595: * query on a delegated catalog.
1596: * @param entityName The name of the entity being searched for, if
1597: * appropriate.
1598: * @param publicId The public identifier of the entity in question.
1599: * @param systemId The nominal system identifier for the entity
1600: * in question (as provided in the source document).
1601: *
1602: * @throws MalformedURLException The formal system identifier of a
1603: * delegated catalog cannot be turned into a valid URL.
1604: * @throws IOException Error reading delegated catalog file.
1605: *
1606: * @return The system identifier to use.
1607: * Note that the nominal system identifier is not returned if a
1608: * match is not found in the catalog, instead null is returned
1609: * to indicate that no match was found.
1610: */
1611: protected synchronized String resolveLocalPublic(int entityType,
1612: String entityName, String publicId, String systemId)
1613: throws MalformedURLException, IOException {
1614:
1615: // Always normalize the public identifier before attempting a match
1616: publicId = PublicId.normalize(publicId);
1617:
1618: // If there's a SYSTEM entry in this catalog, use it
1619: if (systemId != null) {
1620: String resolved = resolveLocalSystem(systemId);
1621: if (resolved != null) {
1622: return resolved;
1623: }
1624: }
1625:
1626: // If there's a PUBLIC entry in this catalog, use it
1627: boolean over = default_override;
1628: Enumeration en = catalogEntries.elements();
1629: while (en.hasMoreElements()) {
1630: CatalogEntry e = (CatalogEntry) en.nextElement();
1631: if (e.getEntryType() == OVERRIDE) {
1632: over = e.getEntryArg(0).equalsIgnoreCase("YES");
1633: continue;
1634: }
1635:
1636: if (e.getEntryType() == PUBLIC
1637: && e.getEntryArg(0).equals(publicId)) {
1638: if (over || systemId == null) {
1639: return e.getEntryArg(1);
1640: }
1641: }
1642: }
1643:
1644: // If there's a DELEGATE_PUBLIC entry in this catalog, use it
1645: over = default_override;
1646: en = catalogEntries.elements();
1647: Vector delCats = new Vector();
1648: while (en.hasMoreElements()) {
1649: CatalogEntry e = (CatalogEntry) en.nextElement();
1650: if (e.getEntryType() == OVERRIDE) {
1651: over = e.getEntryArg(0).equalsIgnoreCase("YES");
1652: continue;
1653: }
1654:
1655: if (e.getEntryType() == DELEGATE_PUBLIC
1656: && (over || systemId == null)) {
1657: String p = (String) e.getEntryArg(0);
1658: if (p.length() <= publicId.length()
1659: && p.equals(publicId.substring(0, p.length()))) {
1660: // delegate this match to the other catalog
1661:
1662: delCats.addElement(e.getEntryArg(1));
1663: }
1664: }
1665: }
1666:
1667: if (delCats.size() > 0) {
1668: Enumeration enCats = delCats.elements();
1669:
1670: if (catalogManager.debug.getDebug() > 1) {
1671: catalogManager.debug.message(2,
1672: "Switching to delegated catalog(s):");
1673: while (enCats.hasMoreElements()) {
1674: String delegatedCatalog = (String) enCats
1675: .nextElement();
1676: catalogManager.debug.message(2, "\t"
1677: + delegatedCatalog);
1678: }
1679: }
1680:
1681: Catalog dcat = newCatalog();
1682:
1683: enCats = delCats.elements();
1684: while (enCats.hasMoreElements()) {
1685: String delegatedCatalog = (String) enCats.nextElement();
1686: dcat.parseCatalog(delegatedCatalog);
1687: }
1688:
1689: return dcat.resolvePublic(publicId, null);
1690: }
1691:
1692: // Nada!
1693: return null;
1694: }
1695:
1696: /**
1697: * Return the applicable SYSTEM system identifier.
1698: *
1699: * <p>If a SYSTEM entry exists in the Catalog
1700: * for the system ID specified, return the mapped value.</p>
1701: *
1702: * <p>On Windows-based operating systems, the comparison between
1703: * the system identifier provided and the SYSTEM entries in the
1704: * Catalog is case-insensitive.</p>
1705: *
1706: * @param systemId The system ID to locate in the catalog.
1707: *
1708: * @return The resolved system identifier.
1709: *
1710: * @throws MalformedURLException The formal system identifier of a
1711: * subordinate catalog cannot be turned into a valid URL.
1712: * @throws IOException Error reading subordinate catalog file.
1713: */
1714: public String resolveSystem(String systemId)
1715: throws MalformedURLException, IOException {
1716:
1717: catalogManager.debug.message(3, "resolveSystem(" + systemId
1718: + ")");
1719:
1720: systemId = normalizeURI(systemId);
1721:
1722: if (systemId != null && systemId.startsWith("urn:publicid:")) {
1723: systemId = PublicId.decodeURN(systemId);
1724: return resolvePublic(systemId, null);
1725: }
1726:
1727: // If there's a SYSTEM entry in this catalog, use it
1728: if (systemId != null) {
1729: String resolved = resolveLocalSystem(systemId);
1730: if (resolved != null) {
1731: return resolved;
1732: }
1733: }
1734:
1735: // Otherwise, look in the subordinate catalogs
1736: return resolveSubordinateCatalogs(SYSTEM, null, null, systemId);
1737: }
1738:
1739: /**
1740: * Return the applicable SYSTEM system identifier in this
1741: * catalog.
1742: *
1743: * <p>If a SYSTEM entry exists in the catalog file
1744: * for the system ID specified, return the mapped value.</p>
1745: *
1746: * @param systemId The system ID to locate in the catalog
1747: *
1748: * @return The mapped system identifier or null
1749: */
1750: protected String resolveLocalSystem(String systemId)
1751: throws MalformedURLException, IOException {
1752:
1753: String osname = System.getProperty("os.name");
1754: boolean windows = (osname.indexOf("Windows") >= 0);
1755: Enumeration en = catalogEntries.elements();
1756: while (en.hasMoreElements()) {
1757: CatalogEntry e = (CatalogEntry) en.nextElement();
1758: if (e.getEntryType() == SYSTEM
1759: && (e.getEntryArg(0).equals(systemId) || (windows && e
1760: .getEntryArg(0).equalsIgnoreCase(systemId)))) {
1761: return e.getEntryArg(1);
1762: }
1763: }
1764:
1765: // If there's a REWRITE_SYSTEM entry in this catalog, use it
1766: en = catalogEntries.elements();
1767: String startString = null;
1768: String prefix = null;
1769: while (en.hasMoreElements()) {
1770: CatalogEntry e = (CatalogEntry) en.nextElement();
1771:
1772: if (e.getEntryType() == REWRITE_SYSTEM) {
1773: String p = (String) e.getEntryArg(0);
1774: if (p.length() <= systemId.length()
1775: && p.equals(systemId.substring(0, p.length()))) {
1776: // Is this the longest prefix?
1777: if (startString == null
1778: || p.length() > startString.length()) {
1779: startString = p;
1780: prefix = e.getEntryArg(1);
1781: }
1782: }
1783: }
1784: }
1785:
1786: if (prefix != null) {
1787: // return the systemId with the new prefix
1788: return prefix + systemId.substring(startString.length());
1789: }
1790:
1791: // If there's a SYSTEM_SUFFIX entry in this catalog, use it
1792: en = catalogEntries.elements();
1793: String suffixString = null;
1794: String suffixURI = null;
1795: while (en.hasMoreElements()) {
1796: CatalogEntry e = (CatalogEntry) en.nextElement();
1797:
1798: if (e.getEntryType() == SYSTEM_SUFFIX) {
1799: String p = (String) e.getEntryArg(0);
1800: if (p.length() <= systemId.length()
1801: && systemId.endsWith(p)) {
1802: // Is this the longest prefix?
1803: if (suffixString == null
1804: || p.length() > suffixString.length()) {
1805: suffixString = p;
1806: suffixURI = e.getEntryArg(1);
1807: }
1808: }
1809: }
1810: }
1811:
1812: if (suffixURI != null) {
1813: // return the systemId for the suffix
1814: return suffixURI;
1815: }
1816:
1817: // If there's a DELEGATE_SYSTEM entry in this catalog, use it
1818: en = catalogEntries.elements();
1819: Vector delCats = new Vector();
1820: while (en.hasMoreElements()) {
1821: CatalogEntry e = (CatalogEntry) en.nextElement();
1822:
1823: if (e.getEntryType() == DELEGATE_SYSTEM) {
1824: String p = (String) e.getEntryArg(0);
1825: if (p.length() <= systemId.length()
1826: && p.equals(systemId.substring(0, p.length()))) {
1827: // delegate this match to the other catalog
1828:
1829: delCats.addElement(e.getEntryArg(1));
1830: }
1831: }
1832: }
1833:
1834: if (delCats.size() > 0) {
1835: Enumeration enCats = delCats.elements();
1836:
1837: if (catalogManager.debug.getDebug() > 1) {
1838: catalogManager.debug.message(2,
1839: "Switching to delegated catalog(s):");
1840: while (enCats.hasMoreElements()) {
1841: String delegatedCatalog = (String) enCats
1842: .nextElement();
1843: catalogManager.debug.message(2, "\t"
1844: + delegatedCatalog);
1845: }
1846: }
1847:
1848: Catalog dcat = newCatalog();
1849:
1850: enCats = delCats.elements();
1851: while (enCats.hasMoreElements()) {
1852: String delegatedCatalog = (String) enCats.nextElement();
1853: dcat.parseCatalog(delegatedCatalog);
1854: }
1855:
1856: return dcat.resolveSystem(systemId);
1857: }
1858:
1859: return null;
1860: }
1861:
1862: /**
1863: * Return the applicable URI.
1864: *
1865: * <p>If a URI entry exists in the Catalog
1866: * for the URI specified, return the mapped value.</p>
1867: *
1868: * <p>URI comparison is case sensitive.</p>
1869: *
1870: * @param uri The URI to locate in the catalog.
1871: *
1872: * @return The resolved URI.
1873: *
1874: * @throws MalformedURLException The system identifier of a
1875: * subordinate catalog cannot be turned into a valid URL.
1876: * @throws IOException Error reading subordinate catalog file.
1877: */
1878: public String resolveURI(String uri) throws MalformedURLException,
1879: IOException {
1880:
1881: catalogManager.debug.message(3, "resolveURI(" + uri + ")");
1882:
1883: uri = normalizeURI(uri);
1884:
1885: if (uri != null && uri.startsWith("urn:publicid:")) {
1886: uri = PublicId.decodeURN(uri);
1887: return resolvePublic(uri, null);
1888: }
1889:
1890: // If there's a URI entry in this catalog, use it
1891: if (uri != null) {
1892: String resolved = resolveLocalURI(uri);
1893: if (resolved != null) {
1894: return resolved;
1895: }
1896: }
1897:
1898: // Otherwise, look in the subordinate catalogs
1899: return resolveSubordinateCatalogs(URI, null, null, uri);
1900: }
1901:
1902: /**
1903: * Return the applicable URI in this catalog.
1904: *
1905: * <p>If a URI entry exists in the catalog file
1906: * for the URI specified, return the mapped value.</p>
1907: *
1908: * @param uri The URI to locate in the catalog
1909: *
1910: * @return The mapped URI or null
1911: */
1912: protected String resolveLocalURI(String uri)
1913: throws MalformedURLException, IOException {
1914: Enumeration en = catalogEntries.elements();
1915: while (en.hasMoreElements()) {
1916: CatalogEntry e = (CatalogEntry) en.nextElement();
1917: if (e.getEntryType() == URI
1918: && (e.getEntryArg(0).equals(uri))) {
1919: return e.getEntryArg(1);
1920: }
1921: }
1922:
1923: // If there's a REWRITE_URI entry in this catalog, use it
1924: en = catalogEntries.elements();
1925: String startString = null;
1926: String prefix = null;
1927: while (en.hasMoreElements()) {
1928: CatalogEntry e = (CatalogEntry) en.nextElement();
1929:
1930: if (e.getEntryType() == REWRITE_URI) {
1931: String p = (String) e.getEntryArg(0);
1932: if (p.length() <= uri.length()
1933: && p.equals(uri.substring(0, p.length()))) {
1934: // Is this the longest prefix?
1935: if (startString == null
1936: || p.length() > startString.length()) {
1937: startString = p;
1938: prefix = e.getEntryArg(1);
1939: }
1940: }
1941: }
1942: }
1943:
1944: if (prefix != null) {
1945: // return the uri with the new prefix
1946: return prefix + uri.substring(startString.length());
1947: }
1948:
1949: // If there's a URI_SUFFIX entry in this catalog, use it
1950: en = catalogEntries.elements();
1951: String suffixString = null;
1952: String suffixURI = null;
1953: while (en.hasMoreElements()) {
1954: CatalogEntry e = (CatalogEntry) en.nextElement();
1955:
1956: if (e.getEntryType() == URI_SUFFIX) {
1957: String p = (String) e.getEntryArg(0);
1958: if (p.length() <= uri.length() && uri.endsWith(p)) {
1959: // Is this the longest prefix?
1960: if (suffixString == null
1961: || p.length() > suffixString.length()) {
1962: suffixString = p;
1963: suffixURI = e.getEntryArg(1);
1964: }
1965: }
1966: }
1967: }
1968:
1969: if (suffixURI != null) {
1970: // return the uri for the suffix
1971: return suffixURI;
1972: }
1973:
1974: // If there's a DELEGATE_URI entry in this catalog, use it
1975: en = catalogEntries.elements();
1976: Vector delCats = new Vector();
1977: while (en.hasMoreElements()) {
1978: CatalogEntry e = (CatalogEntry) en.nextElement();
1979:
1980: if (e.getEntryType() == DELEGATE_URI) {
1981: String p = (String) e.getEntryArg(0);
1982: if (p.length() <= uri.length()
1983: && p.equals(uri.substring(0, p.length()))) {
1984: // delegate this match to the other catalog
1985:
1986: delCats.addElement(e.getEntryArg(1));
1987: }
1988: }
1989: }
1990:
1991: if (delCats.size() > 0) {
1992: Enumeration enCats = delCats.elements();
1993:
1994: if (catalogManager.debug.getDebug() > 1) {
1995: catalogManager.debug.message(2,
1996: "Switching to delegated catalog(s):");
1997: while (enCats.hasMoreElements()) {
1998: String delegatedCatalog = (String) enCats
1999: .nextElement();
2000: catalogManager.debug.message(2, "\t"
2001: + delegatedCatalog);
2002: }
2003: }
2004:
2005: Catalog dcat = newCatalog();
2006:
2007: enCats = delCats.elements();
2008: while (enCats.hasMoreElements()) {
2009: String delegatedCatalog = (String) enCats.nextElement();
2010: dcat.parseCatalog(delegatedCatalog);
2011: }
2012:
2013: return dcat.resolveURI(uri);
2014: }
2015:
2016: return null;
2017: }
2018:
2019: /**
2020: * Search the subordinate catalogs, in order, looking for a match.
2021: *
2022: * <p>This method searches the Catalog and returns the system
2023: * identifier specified for the given entity type with the given
2024: * name, public, and system identifiers. In some contexts, these
2025: * may be null.</p>
2026: *
2027: * @param entityType The CatalogEntry type for which this query is
2028: * being conducted. This is necessary in order to do the approprate
2029: * query on a subordinate catalog.
2030: * @param entityName The name of the entity being searched for, if
2031: * appropriate.
2032: * @param publicId The public identifier of the entity in question
2033: * (as provided in the source document).
2034: * @param systemId The nominal system identifier for the entity
2035: * in question (as provided in the source document). This parameter is
2036: * overloaded for the URI entry type.
2037: *
2038: * @throws MalformedURLException The formal system identifier of a
2039: * delegated catalog cannot be turned into a valid URL.
2040: * @throws IOException Error reading delegated catalog file.
2041: *
2042: * @return The system identifier to use.
2043: * Note that the nominal system identifier is not returned if a
2044: * match is not found in the catalog, instead null is returned
2045: * to indicate that no match was found.
2046: */
2047: protected synchronized String resolveSubordinateCatalogs(
2048: int entityType, String entityName, String publicId,
2049: String systemId) throws MalformedURLException, IOException {
2050:
2051: for (int catPos = 0; catPos < catalogs.size(); catPos++) {
2052: Catalog c = null;
2053:
2054: try {
2055: c = (Catalog) catalogs.elementAt(catPos);
2056: } catch (ClassCastException e) {
2057: String catfile = (String) catalogs.elementAt(catPos);
2058: c = newCatalog();
2059:
2060: try {
2061: c.parseCatalog(catfile);
2062: } catch (MalformedURLException mue) {
2063: catalogManager.debug.message(1,
2064: "Malformed Catalog URL", catfile);
2065: } catch (FileNotFoundException fnfe) {
2066: catalogManager.debug.message(1,
2067: "Failed to load catalog, file not found",
2068: catfile);
2069: } catch (IOException ioe) {
2070: catalogManager.debug.message(1,
2071: "Failed to load catalog, I/O error",
2072: catfile);
2073: }
2074:
2075: catalogs.setElementAt(c, catPos);
2076: }
2077:
2078: String resolved = null;
2079:
2080: // Ok, now what are we supposed to call here?
2081: if (entityType == DOCTYPE) {
2082: resolved = c.resolveDoctype(entityName, publicId,
2083: systemId);
2084: } else if (entityType == DOCUMENT) {
2085: resolved = c.resolveDocument();
2086: } else if (entityType == ENTITY) {
2087: resolved = c.resolveEntity(entityName, publicId,
2088: systemId);
2089: } else if (entityType == NOTATION) {
2090: resolved = c.resolveNotation(entityName, publicId,
2091: systemId);
2092: } else if (entityType == PUBLIC) {
2093: resolved = c.resolvePublic(publicId, systemId);
2094: } else if (entityType == SYSTEM) {
2095: resolved = c.resolveSystem(systemId);
2096: } else if (entityType == URI) {
2097: resolved = c.resolveURI(systemId);
2098: }
2099:
2100: if (resolved != null) {
2101: return resolved;
2102: }
2103: }
2104:
2105: return null;
2106: }
2107:
2108: // -----------------------------------------------------------------
2109:
2110: /**
2111: * Replace backslashes with forward slashes. (URLs always use
2112: * forward slashes.)
2113: *
2114: * @param sysid The input system identifier.
2115: * @return The same system identifier with backslashes turned into
2116: * forward slashes.
2117: */
2118: protected String fixSlashes(String sysid) {
2119: return sysid.replace('\\', '/');
2120: }
2121:
2122: /**
2123: * Construct an absolute URI from a relative one, using the current
2124: * base URI.
2125: *
2126: * @param sysid The (possibly relative) system identifier
2127: * @return The system identifier made absolute with respect to the
2128: * current {@link #base}.
2129: */
2130: protected String makeAbsolute(String sysid) {
2131: URL local = null;
2132:
2133: sysid = fixSlashes(sysid);
2134:
2135: try {
2136: local = new URL(base, sysid);
2137: } catch (MalformedURLException e) {
2138: catalogManager.debug.message(1,
2139: "Malformed URL on system identifier", sysid);
2140: }
2141:
2142: if (local != null) {
2143: return local.toString();
2144: } else {
2145: return sysid;
2146: }
2147: }
2148:
2149: /**
2150: * Perform character normalization on a URI reference.
2151: *
2152: * @param uriref The URI reference
2153: * @return The normalized URI reference.
2154: */
2155: protected String normalizeURI(String uriref) {
2156: String newRef = "";
2157: byte[] bytes;
2158:
2159: if (uriref == null) {
2160: return null;
2161: }
2162:
2163: try {
2164: bytes = uriref.getBytes("UTF-8");
2165: } catch (UnsupportedEncodingException uee) {
2166: // this can't happen
2167: catalogManager.debug.message(1,
2168: "UTF-8 is an unsupported encoding!?");
2169: return uriref;
2170: }
2171:
2172: for (int count = 0; count < bytes.length; count++) {
2173: int ch = bytes[count] & 0xFF;
2174:
2175: if ((ch <= 0x20) // ctrl
2176: || (ch > 0x7F) // high ascii
2177: || (ch == 0x22) // "
2178: || (ch == 0x3C) // <
2179: || (ch == 0x3E) // >
2180: || (ch == 0x5C) // \
2181: || (ch == 0x5E) // ^
2182: || (ch == 0x60) // `
2183: || (ch == 0x7B) // {
2184: || (ch == 0x7C) // |
2185: || (ch == 0x7D) // }
2186: || (ch == 0x7F)) {
2187: newRef += encodedByte(ch);
2188: } else {
2189: newRef += (char) bytes[count];
2190: }
2191: }
2192:
2193: return newRef;
2194: }
2195:
2196: /**
2197: * Perform %-encoding on a single byte.
2198: *
2199: * @param b The 8-bit integer that represents th byte. (Bytes are signed
2200: but encoding needs to look at the bytes unsigned.)
2201: * @return The %-encoded string for the byte in question.
2202: */
2203: protected String encodedByte(int b) {
2204: String hex = Integer.toHexString(b).toUpperCase();
2205: if (hex.length() < 2) {
2206: return "%0" + hex;
2207: } else {
2208: return "%" + hex;
2209: }
2210: }
2211:
2212: // -----------------------------------------------------------------
2213:
2214: /**
2215: * Add to the current list of delegated catalogs.
2216: *
2217: * <p>This method always constructs the {@link #localDelegate}
2218: * vector so that it is ordered by length of partial
2219: * public identifier.</p>
2220: *
2221: * @param entry The DELEGATE catalog entry
2222: */
2223: protected void addDelegate(CatalogEntry entry) {
2224: int pos = 0;
2225: String partial = entry.getEntryArg(0);
2226:
2227: Enumeration local = localDelegate.elements();
2228: while (local.hasMoreElements()) {
2229: CatalogEntry dpe = (CatalogEntry) local.nextElement();
2230: String dp = dpe.getEntryArg(0);
2231: if (dp.equals(partial)) {
2232: // we already have this prefix
2233: return;
2234: }
2235: if (dp.length() > partial.length()) {
2236: pos++;
2237: }
2238: if (dp.length() < partial.length()) {
2239: break;
2240: }
2241: }
2242:
2243: // now insert partial into the vector at [pos]
2244: if (localDelegate.size() == 0) {
2245: localDelegate.addElement(entry);
2246: } else {
2247: localDelegate.insertElementAt(entry, pos);
2248: }
2249: }
2250: }
|