KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > avalon > excalibur > catalog > Catalog


1 /*
2  * Copyright (C) The Apache Software Foundation. All rights reserved.
3  *
4  * This software is published under the terms of the Apache Software License
5  * version 1.1, a copy of which has been included with this distribution in
6  * the LICENSE.txt file.
7  */

8 package org.apache.avalon.excalibur.catalog;
9
10 import java.io.FileNotFoundException;
11 import java.io.IOException;
12 import java.lang.Integer;
13 import java.net.MalformedURLException;
14 import java.net.URL;
15 import java.util.Enumeration;
16 import java.util.Hashtable;
17 import java.util.Vector;
18 import org.xml.sax.SAXException;
19
20 /**
21  * <p>Represents OASIS Open Catalog files.</p>
22  *
23  * This class loads one or more OASIS Open Catalog files (defined by <a
24  * HREF="http://www.oasis-open.org/html/a401.htm">OASIS Technical Resolution
25  * 9401:1997 (Amendment 2 to TR 9401)</a> ) and provides methods for
26  * implementing the Catalog semantics.</p> <p>
27  *
28  * The primary purpose of the Catalog is to associate resources in the document
29  * with local system identifiers. Some entities (document types, XML entities,
30  * and notations) have names and all of them can have either public or system
31  * identifiers or both. (In XML, only a notation can have a public identifier
32  * without a system identifier, but the methods implemented in this class obey
33  * the Catalog semantics from the SGML days when system identifiers were
34  * optional.)</p> <p>
35  *
36  * The system identifiers returned by the resolution methods in this class are
37  * valid, i.e. usable by, and in fact constructed by, the <tt>java.net.URL</tt>
38  * class. Unfortunately, this class seems to behave in somewhat non-standard
39  * ways and the system identifiers returned may not be directly usable in a
40  * browser or filesystem context. <p>
41  *
42  * This class processes the following Catalog entries:</p>
43  * <ul>
44  * <li> <b>BASE</b> changes the base URI for resolving relative system
45  * identifiers. The initial base URI is the URI of the location of the catalog
46  * (which is, in turn, relative to the location of the current working
47  * directory at startup, as returned by the <tt>user.dir</tt> system
48  * property).</li>
49  * <li> <b>CATALOG</b> processes other catalog files. An included catalog
50  * occurs logically at the end of the including catalog.</li>
51  * <li> <b>DELEGATE</b> specifies alternate catalogs for some public
52  * identifiers. The delegated catalogs are not loaded until they are needed,
53  * but they are cached once loaded.</li>
54  * <li> <b>DOCTYPE</b> associates the names of root elements with URIs. (In
55  * other words, an XML processor might infer the doctype of an XML document
56  * that does not include a doctype declaration by looking for the DOCTYPE
57  * entry in the catalog which matches the name of the root element of the
58  * document.)</li>
59  * <li> <b>DOCUMENT</b> provides a default document.</li>
60  * <li> <b>DTDDECL</b> recognized and silently ignored. Not relevant for XML.
61  * </li>
62  * <li> <b>ENTITY</b> associates entity names with URIs.</li>
63  * <li> <b>LINKTYPE</b> recognized and silently ignored. Not relevant for XML.
64  * </li>
65  * <li> <b>NOTATION</b> associates notation names with URIs.</li>
66  * <li> <b>OVERRIDE</b> changes the override behavior. Initial behavior is set
67  * by the system property <tt>xml.catalog.override</tt> . The default initial
68  * behavior is 'YES', that is, entries in the catalog override system
69  * identifiers specified in the document.</li>
70  * <li> <b>PUBLIC</b> maps a public identifier to a system identifier.</li>
71  *
72  * <li> <b>SGMLDECL</b> recognized and silently ignored. Not relevant for XML.
73  * </li>
74  * <li> <b>SYSTEM</b> maps a system identifier to another system identifier.
75  * </li>
76  * </ul>
77  * <p>
78  *
79  * Note that subordinate catalogs (all catalogs except the first, including
80  * CATALOG and DELEGATE catalogs) are only loaded if and when they are required.
81  * </p><p>
82  *
83  * If provided with an SAX Parser class, this object can also load XML Catalogs.
84  * For the details about which XML Catalog formats are recognized, see {@link
85  * XMLCatalogReader}. <p>
86  *
87  * This code interrogates the following non-standard system properties:</p> <dl>
88  * <dt><b>xml.catalog.debug</b></dt> <dd><p>
89  *
90  * Sets the debug level. A value of 0 is assumed if the property is not set or
91  * is not a number.</p></dd> <dt><b>xml.catalog.override</b></dt> <dd><p>
92  *
93  * Specifies the default override behavior. If override is true ("true", "yes",
94  * "1"), system identifiers in the catalog file are used in preference to system
95  * identifiers in the document. In other words, a value of false essentially
96  * disables catalog processing since almost all external entities are required
97  * to have a system identifier in XML. A value of true is assumed if the
98  * property is not set.</p></dd> <dt><b>xml.catalog.files</b></dt> <dd><p>
99  *
100  * Identifies the list of catalog <i>files</i> to parse initially. (Additional
101  * catalog files may be parsed if the CATALOG entry is used.) Components of the
102  * list should be separated by the system property " <code>path.separator
103  * </code>" character (typically ";" on DOS/Windows systems, ":" on Unix
104  * systems).</p> <p>
105  *
106  * Additional catalogs may also be loaded with the {@link #parseCatalog} method.
107  * </p</dd></dl> <p>
108  *
109  * <b>Change Log:</b></p> <dl><dt>1.0.1</dt> <dd><p>
110  *
111  * Fixed a bug in the calculation of the list of subordinate catalogs. This bug
112  * caused an infinite loop where parsing would alternately process two catalogs
113  * indefinitely.</p></dd></dl>
114  *
115  * @author Abortext, Inc.
116  * @author <a HREF="mailto:nwalsh@arbortext.com">Norman Walsh</a>
117  * @version 1.0.1
118  * @see CatalogReader
119  * @see XMLCatalogReader
120  * @see CatalogEntry
121  */

122 public class Catalog
123 {
124     /**
125      * <p>
126      *
127      * The debug level.</p> <p>
128      *
129      * In general, higher numbers produce more information:</p>
130      * <ul>
131      * <li> 0, no messages
132      * <li> 1, minimal messages (high-level status)
133      * <li> 2, more messages
134      * <li> 3, detailed messages
135      * </ul>
136      *
137      */

138     public int debug = 0;
139
140     /**
141      * The base URI for relative system identifiers in the catalog. This may be
142      * changed by BASE entries in the catalog.
143      */

144     private URL base;
145
146     /**
147      * The base URI of the Catalog file currently being parsed.
148      */

149     private URL catalogCwd;
150
151     /**
152      * The catalog entries currently known to the system.
153      */

154     private Vector catalogEntries = new Vector();
155
156     /**
157      * The default initial override setting.
158      */

159     private boolean default_override = true;
160
161     /**
162      * <p>
163      *
164      * A vector of catalog files to be loaded.</p> <p>
165      *
166      * This list is initially established by <code>loadSystemCatalogs</code>
167      * when it parses the system catalog list, but CATALOG entries may
168      * contribute to it during the course of parsing.</p>
169      *
170      * @see #loadSystemCatalogs
171      * @see localCatalogFiles
172      */

173     private Vector catalogFiles = new Vector();
174
175     /**
176      * <p>
177      *
178      * A vector of catalog files constructed during processing of CATALOG
179      * entries in the current catalog.</p> <p>
180      *
181      * This two-level system is actually necessary to correctly implement the
182      * semantics of the CATALOG entry. If one catalog file includes another with
183      * a CATALOG entry, the included catalog logically occurs <i>at the end</i>
184      * of the including catalog, and after any preceding CATALOG entries. In
185      * other words, the CATALOG entry cannot insert anything into the middle of
186      * a catalog file.</p> <p>
187      *
188      * When processing reaches the end of each catalog files, any elements on
189      * this vector are added to the front of the <code>catalogFiles</code>
190      * vector.</p>
191      *
192      * @see catalogFiles
193      */

194     private Vector localCatalogFiles = new Vector();
195
196     /**
197      * <p>
198      *
199      * A vector of Catalogs.</p> <p>
200      *
201      * The semantics of Catalog resolution are such that each catalog is
202      * effectively a list of Catalogs (in other words, a recursive list of
203      * Catalog instances).</p> <p>
204      *
205      * Catalogs that are processed as the result of CATALOG or DELEGATE entries
206      * are subordinate to the catalog that contained them, but they may in turn
207      * have subordinate catalogs.</p> <p>
208      *
209      * Catalogs are only loaded when they are needed, so this vector initially
210      * contains a list of Catalog filenames (URLs). If, during processing, one
211      * of these catalogs has to be loaded, the resulting Catalog object is
212      * placed in the vector, effectively caching it for the next query.</p>
213      */

214     private Vector catalogs = new Vector();
215
216     /**
217      * <p>
218      *
219      * A vector of DELEGATE Catalog entries constructed during processing of the
220      * Catalog.</p> <p>
221      *
222      * This two-level system has two purposes; first, it allows us to sort the
223      * DELEGATE entries by the length of the partial public identifier so that a
224      * linear search encounters them in the correct order and second, it puts
225      * them all at the end of the Catalog.</p> <p>
226      *
227      * When processing reaches the end of each catalog file, any elements on
228      * this vector are added to the end of the <code>catalogEntries</code>
229      * vector. This assures that matching PUBLIC keywords are encountered before
230      * DELEGATE entries.</p>
231      */

232     private Vector localDelegate = new Vector();
233
234     /**
235      * <p>
236      *
237      * The name of the parser class to load when parsing XML Catalogs.</p> <p>
238      *
239      * If a parser class is provided, subsequent attempts to parse Catalog files
240      * will begin by attemptiing an XML parse of the catalog file using a parser
241      * of this class. If the XML parse fails, the "default" text parse will be
242      * done instead.</p>
243      */

244     private String parserClass = null;
245
246     /**
247      * <p>
248      *
249      * Constructs an empty Catalog.</p> <p>
250      *
251      * The constructor interrogates the relevant system properties and
252      * initializes the catalog data structures.</p>
253      */

254     public Catalog()
255     {
256         String property = System.getProperty( "xml.catalog.debug" );
257
258         if( property != null )
259         {
260             try
261             {
262                 debug = Integer.parseInt( property );
263             }
264             catch( NumberFormatException e )
265             {
266                 debug = 0;
267             }
268         }
269
270         property = System.getProperty( "xml.catalog.override" );
271
272         if( property != null )
273         {
274             default_override = ( property.equalsIgnoreCase( "true" )
275                  || property.equalsIgnoreCase( "yes" )
276                  || property.equalsIgnoreCase( "1" ) );
277         }
278     }
279
280     /**
281      * <p>
282      *
283      * Load the system catalog files.</p> <p>
284      *
285      * The method adds all of the catalogs specified in the <tt>
286      * xml.catalog.files</tt> property to the Catalog list.</p>
287      *
288      * @throws MalformedURLException One of the system catalogs is identified
289      * with a filename that is not a valid URL.
290      * @throws IOException One of the system catalogs cannot be read.
291      */

292     public void loadSystemCatalogs()
293         throws MalformedURLException, IOException
294     {
295         String PCS = System.getProperty( "path.separator" );
296         String catalog_files = System.getProperty( "xml.catalog.files" );
297
298         while( catalog_files != null )
299         {
300             int pos = catalog_files.indexOf( PCS );
301             String catfile = null;
302
303             if( pos > 0 )
304             {
305                 catfile = catalog_files.substring( 0, pos );
306                 catalog_files = catalog_files.substring( pos + 1 );
307             }
308             else
309             {
310                 catfile = catalog_files;
311                 catalog_files = null;
312             }
313
314             catalogFiles.addElement( catfile );
315         }
316
317         if( catalogFiles.size() > 0 )
318         {
319             // This is a little odd. The parseCatalog() method expects
320
// a filename, but it adds that name to the end of the
321
// catalogFiles vector, and then processes that vector.
322
// This allows the system to handle CATALOG entries
323
// correctly.
324
//
325
// In this init case, we take the last element off the
326
// catalogFiles vector and pass it to parseCatalog. This
327
// will "do the right thing" in the init case, and allow
328
// parseCatalog() to do the right thing in the non-init
329
// case. Honest.
330
//
331
String catfile = (String)catalogFiles.lastElement();
332             catalogFiles.removeElement( catfile );
333             parseCatalog( catfile );
334         }
335     }
336
337     /**
338      * <p>
339      *
340      * Parse a catalog file, augmenting internal data structures</p>
341      *
342      * @param fileName The filename of the catalog file to process
343      * @throws MalformedURLException The fileName cannot be turned into a valid
344      * URL.
345      * @throws IOException Error reading catalog file.
346      */

347     public synchronized void parseCatalog( String fileName )
348         throws MalformedURLException, IOException
349     {
350
351         // Put the file into the list of catalogs to process...
352
// In all cases except the case when initCatalog() is the
353
// caller, this will be the only catalog initially in the list...
354
catalogFiles.addElement( fileName );
355
356         // Now process all the files on the catalogFiles vector. This
357
// vector can grow during processing if CATALOG entries are
358
// encountered in the catalog
359
int curCat = 0;
360         while( curCat < catalogFiles.size() )
361         {
362             String catfile = (String)catalogFiles.elementAt( curCat++ );
363
364             if( catalogEntries.size() == 0 && catalogs.size() == 0 )
365             {
366                 // We haven't parsed any catalogs yet, let this
367
// catalog be the first...
368
parseCatalogFile( catfile );
369             }
370             else
371             {
372                 // This is a subordinate catalog. We save its name,
373
// but don't bother to load it unless it's necessary.
374
catalogs.addElement( catfile );
375             }
376
377             if( !localCatalogFiles.isEmpty() )
378             {
379                 // Move all the localCatalogFiles into the front of
380
// the catalogFiles queue
381
Vector newQueue = new Vector();
382                 Enumeration q = localCatalogFiles.elements();
383                 while( q.hasMoreElements() )
384                 {
385                     newQueue.addElement( q.nextElement() );
386                 }
387
388                 // Put the rest of the catalogs on the end of the new list
389
while( curCat < catalogFiles.size() )
390                 {
391                     catfile = (String)catalogFiles.elementAt( curCat++ );
392                     newQueue.addElement( catfile );
393                 }
394
395                 localCatalogFiles = new Vector();
396                 catalogFiles = newQueue;
397                 curCat = 0;
398             }
399
400             if( !localDelegate.isEmpty() )
401             {
402                 Enumeration e = localDelegate.elements();
403                 while( e.hasMoreElements() )
404                 {
405                     catalogEntries.addElement( e.nextElement() );
406                 }
407                 localDelegate = new Vector();
408             }
409         }
410
411         // We've parsed them all, reinit the vector...
412
catalogFiles = new Vector();
413     }
414
415     /**
416      * <p>
417      *
418      * Parse all subordinate catalogs.</p> <p>
419      *
420      * This method recursively parses all of the subordinate catalogs. If this
421      * method does not throw an exception, you can be confident that no
422      * subsequent call to any resolve*() method will either, with two possible
423      * exceptions:</p>
424      * <ol>
425      * <li> <p>
426      *
427      * Delegated catalogs are re-parsed each time they are needed (because a
428      * variable list of them may be needed in each case, depending on the
429      * length of the matching partial public identifier).</p> <p>
430      *
431      * But they are parsed by this method, so as long as they don't change or
432      * disappear while the program is running, they shouldn't generate errors
433      * later if they don't generate errors now.</p>
434      * <li> <p>
435      *
436      * If you add new catalogs with <code>parseCatalog</code> , they won't be
437      * loaded until they are needed or until you call <code>parseAllCatalogs
438      * </code>again.</p>
439      * </ol>
440      * <p>
441      *
442      * On the other hand, if you don't call this method, you may successfully
443      * parse documents without having to load all possible catalogs.</p>
444      *
445      * @throws MalformedURLException The filename (URL) for a subordinate or
446      * delegated catalog is not a valid URL.
447      * @throws IOException Error reading some subordinate or delegated catalog
448      * file.
449      */

450     public void parseAllCatalogs()
451         throws MalformedURLException, IOException
452     {
453
454         // Parse all the subordinate catalogs
455
for( int catPos = 0; catPos < catalogs.size(); catPos++ )
456         {
457             Catalog c = null;
458
459             try
460             {
461                 c = (Catalog)catalogs.elementAt( catPos );
462             }
463             catch( ClassCastException e )
464             {
465                 String catfile = (String)catalogs.elementAt( catPos );
466                 c = new Catalog();
467                 c.setParserClass( parserClass );
468                 c.debug = debug;
469
470                 c.parseCatalog( catfile );
471                 catalogs.setElementAt( c, catPos );
472                 c.parseAllCatalogs();
473             }
474         }
475
476         // Parse all the DELEGATE catalogs
477
Enumeration enum = catalogEntries.elements();
478         while( enum.hasMoreElements() )
479         {
480             CatalogEntry e = (CatalogEntry)enum.nextElement();
481             if( e.entryType() == CatalogEntry.DELEGATE )
482             {
483                 Catalog dcat = new Catalog();
484                 dcat.setParserClass( parserClass );
485                 dcat.debug = debug;
486                 dcat.parseCatalog( e.formalSystemIdentifier() );
487             }
488         }
489     }
490
491
492     /**
493      * <p>
494      *
495      * Return the applicable DOCTYPE system identifier.</p>
496      *
497      * @param entityName The name of the entity (element) for which a doctype is
498      * required.
499      * @param publicId The nominal public identifier for the doctype (as
500      * provided in the source document).
501      * @param systemId The nominal system identifier for the doctype (as
502      * provided in the source document).
503      * @return The system identifier to use for the doctype.
504      * @throws MalformedURLException The formal system identifier of a
505      * subordinate catalog cannot be turned into a valid URL.
506      * @throws IOException Error reading subordinate catalog file.
507      */

508     public String resolveDoctype( String entityName,
509                                   String publicId,
510                                   String systemId )
511         throws MalformedURLException, IOException
512     {
513         String resolved = null;
514
515         if( systemId != null )
516         {
517             // If there's a SYSTEM entry in this catalog, use it
518
resolved = resolveLocalSystem( systemId );
519             if( resolved != null )
520             {
521                 return resolved;
522             }
523         }
524
525         if( publicId != null )
526         {
527             // If there's a PUBLIC entry in this catalog, use it
528
resolved = resolveLocalPublic( CatalogEntry.DOCTYPE,
529                 entityName,
530                 publicId,
531                 systemId );
532             if( resolved != null )
533             {
534                 return resolved;
535             }
536         }
537
538         // If there's a DOCTYPE entry in this catalog, use it
539
boolean over = default_override;
540         Enumeration enum = catalogEntries.elements();
541         while( enum.hasMoreElements() )
542         {
543             CatalogEntry e = (CatalogEntry)enum.nextElement();
544             if( e.entryType() == CatalogEntry.OVERRIDE )
545             {
546                 over = e.yes_or_no().equalsIgnoreCase( "YES" );
547                 continue;
548             }
549
550             if( e.entryType() == CatalogEntry.DOCTYPE
551                  && e.entityName().equals( entityName ) )
552             {
553                 if( over || systemId == null )
554                 {
555                     return e.formalSystemIdentifier();
556                 }
557             }
558         }
559
560         // Otherwise, look in the subordinate catalogs
561
return resolveSubordinateCatalogs( CatalogEntry.DOCTYPE,
562             entityName,
563             publicId,
564             systemId );
565     }
566
567     /**
568      * <p>
569      *
570      * Return the applicable DOCUMENT entry.</p>
571      *
572      * @return The system identifier to use for the doctype.
573      * @throws MalformedURLException The formal system identifier of a
574      * subordinate catalog cannot be turned into a valid URL.
575      * @throws IOException Error reading subordinate catalog file.
576      */

577     public String resolveDocument()
578         throws MalformedURLException, IOException
579     {
580         // If there's a DOCUMENT entry, return it
581
Enumeration enum = catalogEntries.elements();
582         while( enum.hasMoreElements() )
583         {
584             CatalogEntry e = (CatalogEntry)enum.nextElement();
585             if( e.entryType() == CatalogEntry.DOCUMENT )
586             {
587                 return e.formalSystemIdentifier();
588             }
589         }
590
591         return resolveSubordinateCatalogs( CatalogEntry.DOCUMENT,
592             null, null, null );
593     }
594
595     /**
596      * <p>
597      *
598      * Return the applicable ENTITY system identifier.</p>
599      *
600      * @param entityName The name of the entity for which a system identifier is
601      * required.
602      * @param publicId The nominal public identifier for the entity (as provided
603      * in the source document).
604      * @param systemId The nominal system identifier for the entity (as provided
605      * in the source document).
606      * @return The system identifier to use for the entity.
607      * @throws MalformedURLException The formal system identifier of a
608      * subordinate catalog cannot be turned into a valid URL.
609      * @throws IOException Error reading subordinate catalog file.
610      */

611     public String resolveEntity( String entityName,
612                                  String publicId,
613                                  String systemId )
614         throws MalformedURLException, IOException
615     {
616         String resolved = null;
617
618         if( systemId != null )
619         {
620             // If there's a SYSTEM entry in this catalog, use it
621
resolved = resolveLocalSystem( systemId );
622             if( resolved != null )
623             {
624                 return resolved;
625             }
626         }
627
628         if( publicId != null )
629         {
630             // If there's a PUBLIC entry in this catalog, use it
631
resolved = resolveLocalPublic( CatalogEntry.ENTITY,
632                 entityName,
633                 publicId,
634                 systemId );
635             if( resolved != null )
636             {
637                 return resolved;
638             }
639         }
640
641         // If there's a ENTITY entry in this catalog, use it
642
boolean over = default_override;
643         Enumeration enum = catalogEntries.elements();
644         while( enum.hasMoreElements() )
645         {
646             CatalogEntry e = (CatalogEntry)enum.nextElement();
647             if( e.entryType() == CatalogEntry.OVERRIDE )
648             {
649                 over = e.yes_or_no().equalsIgnoreCase( "YES" );
650                 continue;
651             }
652
653             if( e.entryType() == CatalogEntry.ENTITY
654                  && e.entityName().equals( entityName ) )
655             {
656                 if( over || systemId == null )
657                 {
658                     return e.formalSystemIdentifier();
659                 }
660             }
661         }
662
663         // Otherwise, look in the subordinate catalogs
664
return resolveSubordinateCatalogs( CatalogEntry.ENTITY,
665             entityName,
666             publicId,
667             systemId );
668     }
669
670     /**
671      * <p>
672      *
673      * Return the applicable NOTATION system identifier.</p>
674      *
675      * @param notationName The name of the notation for which a doctype is
676      * required.
677      * @param publicId The nominal public identifier for the notation (as
678      * provided in the source document).
679      * @param systemId The nominal system identifier for the notation (as
680      * provided in the source document).
681      * @return The system identifier to use for the notation.
682      * @throws MalformedURLException The formal system identifier of a
683      * subordinate catalog cannot be turned into a valid URL.
684      * @throws IOException Error reading subordinate catalog file.
685      */

686     public String resolveNotation( String notationName,
687                                    String publicId,
688                                    String systemId )
689         throws MalformedURLException, IOException
690     {
691         String resolved = null;
692
693         if( systemId != null )
694         {
695             // If there's a SYSTEM entry in this catalog, use it
696
resolved = resolveLocalSystem( systemId );
697             if( resolved != null )
698             {
699                 return resolved;
700             }
701         }
702
703         if( publicId != null )
704         {
705             // If there's a PUBLIC entry in this catalog, use it
706
resolved = resolveLocalPublic( CatalogEntry.NOTATION,
707                 notationName,
708                 publicId,
709                 systemId );
710             if( resolved != null )
711             {
712                 return resolved;
713             }
714         }
715
716         // If there's a NOTATION entry in this catalog, use it
717
boolean over = default_override;
718         Enumeration enum = catalogEntries.elements();
719         while( enum.hasMoreElements() )
720         {
721             CatalogEntry e = (CatalogEntry)enum.nextElement();
722             if( e.entryType() == CatalogEntry.OVERRIDE )
723             {
724                 over = e.yes_or_no().equalsIgnoreCase( "YES" );
725                 continue;
726             }
727
728             if( e.entryType() == CatalogEntry.NOTATION
729                  && e.entityName().equals( notationName ) )
730             {
731                 if( over || systemId == null )
732                 {
733                     return e.formalSystemIdentifier();
734                 }
735             }
736         }
737
738         // Otherwise, look in the subordinate catalogs
739
return resolveSubordinateCatalogs( CatalogEntry.NOTATION,
740             notationName,
741             publicId,
742             systemId );
743     }
744
745     /**
746      * <p>
747      *
748      * Return the applicable PUBLIC or SYSTEM identifier.</p> <p>
749      *
750      * This method searches the Catalog and returns the system identifier
751      * specified for the given system or public identifiers. If no appropriate
752      * PUBLIC or SYSTEM entry is found in the Catalog, null is returned.</p>
753      *
754      * @param publicId The public identifier to locate in the catalog. Public
755      * identifiers are normalized before comparison.
756      * @param systemId The nominal system identifier for the entity in question
757      * (as provided in the source document).
758      * @return The system identifier to use. Note that the nominal system
759      * identifier is not returned if a match is not found in the catalog,
760      * instead null is returned to indicate that no match was found.
761      * @throws MalformedURLException The formal system identifier of a
762      * subordinate catalog cannot be turned into a valid URL.
763      * @throws IOException Error reading subordinate catalog file.
764      */

765     public String resolvePublic( String publicId, String systemId )
766         throws MalformedURLException, IOException
767     {
768
769         // If there's a SYSTEM entry in this catalog, use it
770
if( systemId != null )
771         {
772             String resolved = resolveLocalSystem( systemId );
773             if( resolved != null )
774             {
775                 return resolved;
776             }
777         }
778
779         // If there's a PUBLIC entry in this catalog, use it
780
String resolved = resolveLocalPublic( CatalogEntry.PUBLIC,
781             null,
782             publicId,
783             systemId );
784         if( resolved != null )
785         {
786             return resolved;
787         }
788
789         // Otherwise, look in the subordinate catalogs
790
return resolveSubordinateCatalogs( CatalogEntry.PUBLIC,
791             null,
792             publicId,
793             systemId );
794     }
795
796     /**
797      * <p>
798      *
799      * Return the applicable SYSTEM system identifier</p> <p>
800      *
801      * If a SYSTEM entry exists in the Catalog for the system ID specified,
802      * return the mapped value.</p> <p>
803      *
804      * The caller is responsible for doing any necessary normalization of the
805      * system identifier before calling this method. For example, a relative
806      * system identifier in a document might be converted to an absolute system
807      * identifier before attempting to resolve it.</p> <p>
808      *
809      * On Windows-based operating systems, the comparison between the system
810      * identifier provided and the SYSTEM entries in the Catalog is
811      * case-insensitive.</p>
812      *
813      * @param systemId The system ID to locate in the catalog.
814      * @return The system identifier to use for the notation.
815      * @throws MalformedURLException The formal system identifier of a
816      * subordinate catalog cannot be turned into a valid URL.
817      * @throws IOException Error reading subordinate catalog file.
818      */

819     public String resolveSystem( String systemId )
820         throws MalformedURLException, IOException
821     {
822
823         // If there's a SYSTEM entry in this catalog, use it
824
if( systemId != null )
825         {
826             String resolved = resolveLocalSystem( systemId );
827             if( resolved != null )
828             {
829                 return resolved;
830             }
831         }
832
833         // Otherwise, look in the subordinate catalogs
834
return resolveSubordinateCatalogs( CatalogEntry.SYSTEM,
835             null,
836             null,
837             systemId );
838     }
839
840     /**
841      * <p>
842      *
843      * Sets the parser class, enabling XML Catalog parsing.</p> <p>
844      *
845      * Sets the parser class that will be used for loading XML Catalogs. If this
846      * method is not called, all catalogs will be parsed as plain text (and
847      * assumed to conform to the <a HREF="http://www.oasis-open.org/html/a401.htm">
848      * OASIS Catalog format</a> ).</p>
849      *
850      * @param parser The name of a class implementing the SAX Parser interface
851      * to be used for subsequent XML Catalog parsing.
852      */

853     public void setParserClass( String parser )
854     {
855         parserClass = parser;
856     }
857
858     /**
859      * <p>
860      *
861      * Parse a single catalog file, augmenting internal data structures</p>
862      *
863      * @param fileName The filename of the catalog file to process
864      * @throws MalformedURLException The fileName cannot be turned into a valid
865      * URL.
866      * @throws IOException Error reading catalog file.
867      */

868     private synchronized void parseCatalogFile( String fileName )
869         throws MalformedURLException, IOException
870     {
871
872         CatalogEntry entry;
873
874         // The base-base is the cwd. If the catalog file is specified
875
// with a relative path, this assures that it gets resolved
876
// properly...
877
try
878         {
879             // tack on a basename because URLs point to files not dirs
880
String userdir = fixSlashes( System.getProperty( "user.dir" ) );
881             catalogCwd = new URL( new StringBuffer("file:///").append(userdir).append("/basename").toString() );
882         }
883         catch( MalformedURLException e )
884         {
885             String userdir = fixSlashes( System.getProperty( "user.dir" ) );
886             debug( 1, "Malformed URL on cwd", userdir );
887             catalogCwd = null;
888         }
889
890         // The initial base URI is the location of the catalog file
891
try
892         {
893             base = new URL( catalogCwd, fixSlashes( fileName ) );
894         }
895         catch( MalformedURLException e )
896         {
897             try
898             {
899                 base = new URL( "file:///" + fixSlashes( fileName ) );
900             }
901             catch( MalformedURLException e2 )
902             {
903                 debug( 1, "Malformed URL on catalog filename",
904                     fixSlashes( fileName ) );
905                 base = null;
906             }
907         }
908
909         debug( 1, "Loading catalog", fileName );
910         debug( 3, "Default BASE", base.toString() );
911
912         fileName = base.toString();
913
914         if( parserClass != null )
915         {
916             try
917             {
918                 XMLCatalogReader catfile = new XMLCatalogReader();
919                 catfile.setParserClass( parserClass );
920                 catfile.parseCatalog( fileName );
921
922                 CatalogEntry ce = null;
923                 while( ( ce = catfile.nextEntry() ) != null )
924                 {
925                     addEntry( ce );
926                 }
927                 return;
928             }
929             catch( SAXException e1 )
930             {
931                 // not an XML catalog, continue with text parse
932
}
933             catch( NoXMLParserException e2 )
934             {
935                 // not an XML catalog, continue with text parse
936
}
937             catch( NotXMLCatalogException e2 )
938             {
939                 // not an XML catalog, continue with text parse
940
}
941             catch( InstantiationException e3 )
942             {
943                 debug( 1, "Cannot instantiate XML Parser class", parserClass );
944             }
945             catch( IllegalAccessException e4 )
946             {
947                 debug( 1, "Cannot access XML Parser class", parserClass );
948             }
949             catch( ClassNotFoundException e5 )
950             {
951                 debug( 1, "Cannot load XML Parser class", parserClass );
952             }
953             catch( UnknownCatalogFormatException e6 )
954             {
955                 debug( 1, "Unrecognized XML Catalog format." );
956