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                 return;
957             }
958         }
959
960         CatalogReader catfile = new CatalogReader();
961         catfile.parseCatalog( fileName );
962
963         // Process the contents of the catalog file as a whitespace
964
// delimited set of tokens
965
while( ( entry = catfile.nextEntry() ) != null )
966         {
967             addEntry( entry );
968         }
969     }
970
971     /**
972      * <p>
973      *
974      * Cleanup and process a Catalog entry.</p> <p>
975      *
976      * This method processes each Catalog entry, changing mapped relative system
977      * identifiers into absolute ones (based on the current base URI), and
978      * maintaining other information about the current catalog.</p>
979      *
980      * @param entry The CatalogEntry to process.
981      */

982     private void addEntry( CatalogEntry entry )
983     {
984         switch ( entry.entryType() )
985         {
986             case CatalogEntry.BASE:
987             {
988                 String value = entry.formalSystemIdentifier();
989                 URL newbase = null;
990
991                 debug( 3, "BASE", value );
992
993                 try
994                 {
995                     value = fixSlashes( value );
996                     newbase = new URL( catalogCwd, value );
997                 }
998                 catch( MalformedURLException e )
999                 {
1000                    try
1001                    {
1002                        newbase = new URL( "file:///" + value );
1003                    }
1004                    catch( MalformedURLException e2 )
1005                    {
1006                        debug( 1, "Malformed URL on base", value );
1007                        newbase = null;
1008                    }
1009                }
1010
1011                if( newbase != null )
1012                {
1013                    base = newbase;
1014                }
1015
1016                break;
1017            }
1018
1019            case CatalogEntry.CATALOG:
1020            {
1021                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1022
1023                debug( 3, "CATALOG", fsi );
1024
1025                localCatalogFiles.addElement( fsi );
1026                break;
1027            }
1028
1029            case CatalogEntry.DOCUMENT:
1030            {
1031                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1032                entry.updateFormalSystemIdentifier( fsi );
1033
1034                debug( 3, "DOCUMENT", fsi );
1035
1036                catalogEntries.addElement( entry );
1037                break;
1038            }
1039            case CatalogEntry.OVERRIDE:
1040            {
1041                debug( 3, "OVERRIDE", entry.yes_or_no() );
1042
1043                catalogEntries.addElement( entry );
1044                break;
1045            }
1046            case CatalogEntry.SGMLDECL:
1047            {
1048                // meaningless in XML
1049
break;
1050            }
1051            case CatalogEntry.DELEGATE:
1052            {
1053                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1054                entry.updateFormalSystemIdentifier( fsi );
1055
1056                debug( 3, "DELEGATE", entry.partialPublicId(), fsi );
1057
1058                addDelegate( entry );
1059                break;
1060            }
1061            case CatalogEntry.DOCTYPE:
1062            {
1063                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1064                entry.updateFormalSystemIdentifier( fsi );
1065
1066                debug( 3, "DOCTYPE", entry.publicId(), fsi );
1067
1068                catalogEntries.addElement( entry );
1069                break;
1070            }
1071            case CatalogEntry.DTDDECL:
1072            {
1073                // meaningless in XML
1074
break;
1075            }
1076            case CatalogEntry.ENTITY:
1077            {
1078                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1079                entry.updateFormalSystemIdentifier( fsi );
1080
1081                debug( 3, "ENTITY", entry.entityName(), fsi );
1082
1083                catalogEntries.addElement( entry );
1084                break;
1085            }
1086            case CatalogEntry.LINKTYPE:
1087            {
1088                // meaningless in XML
1089
break;
1090            }
1091            case CatalogEntry.NOTATION:
1092            {
1093                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1094                entry.updateFormalSystemIdentifier( fsi );
1095
1096                debug( 3, "NOTATION", entry.entityName(), fsi );
1097
1098                catalogEntries.addElement( entry );
1099                break;
1100            }
1101            case CatalogEntry.PUBLIC:
1102            {
1103                // This entry has to go in the vector because it would
1104
// be relevant in subsequent searches for notations.
1105
String publicid = entry.publicId();
1106                String systemid = makeAbsolute( entry.formalSystemIdentifier() );
1107
1108                debug( 3, "PUBLIC", publicid, systemid );
1109
1110                entry.updateFormalSystemIdentifier( systemid );
1111                catalogEntries.addElement( entry );
1112                break;
1113            }
1114            case CatalogEntry.SYSTEM:
1115            {
1116                String systemid = entry.systemId();
1117                String fsi = makeAbsolute( entry.formalSystemIdentifier() );
1118
1119                debug( 3, "SYSTEM", systemid, fsi );
1120
1121                entry.updateFormalSystemIdentifier( fsi );
1122                catalogEntries.addElement( entry );
1123                break;
1124            }
1125        }
1126    }
1127
1128    /**
1129     * <p>
1130     *
1131     * Return the applicable PUBLIC or SYSTEM identifier</p> <p>
1132     *
1133     * This method searches the Catalog and returns the system identifier
1134     * specified for the given system or public identifiers. If no appropriate
1135     * PUBLIC or SYSTEM entry is found in the Catalog, delegated Catalogs are
1136     * interrogated.</p> <p>
1137     *
1138     * There are four possible cases:</p>
1139     * <ul>
1140     * <li> If the system identifier provided matches a SYSTEM entry in the
1141     * current catalog, the SYSTEM entry is returned.
1142     * <li> If the system identifier is not null, the PUBLIC entries that were
1143     * encountered when OVERRIDE YES was in effect are interrogated and the
1144     * first matching entry is returned.</li>
1145     * <li> If the system identifier is null, then all of the PUBLIC entries
1146     * are interrogated and the first matching entry is returned. This may not
1147     * be the same as the preceding case, if some PUBLIC entries are
1148     * encountered when OVERRIDE NO is in effect. In XML, the only place where
1149     * a public identifier may occur without a system identifier is in a
1150     * notation declaration.</li>
1151     * <li> Finally, if the public identifier matches one of the partial
1152     * public identifiers specified in a DELEGATE entry in the Catalog, the
1153     * delegated catalog is interrogated. The first time that the delegated
1154     * catalog is required, it will be retrieved and parsed. It is
1155     * subsequently cached.</li>
1156     * </ul>
1157     *
1158     *
1159     * @param entityType The CatalogEntry type for which this query is being
1160     * conducted. This is necessary in order to do the approprate query on
1161     * a delegated catalog.
1162     * @param entityName The name of the entity being searched for, if
1163     * appropriate.
1164     * @param publicId The public identifier of the entity in question.
1165     * @param systemId The nominal system identifier for the entity in question
1166     * (as provided in the source document).
1167     * @return The system identifier to use. Note that the nominal system
1168     * identifier is not returned if a match is not found in the catalog,
1169     * instead null is returned to indicate that no match was found.
1170     * @throws MalformedURLException The formal system identifier of a delegated
1171     * catalog cannot be turned into a valid URL.
1172     * @throws IOException Error reading delegated catalog file.
1173     */

1174    private synchronized String resolveLocalPublic( int entityType,
1175                                                    String entityName,
1176                                                    String publicId,
1177                                                    String systemId )
1178        throws MalformedURLException, IOException
1179    {
1180
1181        // Always normalize the public identifier before attempting a match
1182
publicId = CatalogReader.normalize( publicId );
1183
1184        // If there's a SYSTEM entry in this catalog, use it
1185
if( systemId != null )
1186        {
1187            String resolved = resolveLocalSystem( systemId );
1188            if( resolved != null )
1189            {
1190                return resolved;
1191            }
1192        }
1193
1194        // If there's a PUBLIC entry in this catalog, use it
1195
boolean over = default_override;
1196        Enumeration enum = catalogEntries.elements();
1197        while( enum.hasMoreElements() )
1198        {
1199            CatalogEntry e = (CatalogEntry)enum.nextElement();
1200            if( e.entryType() == CatalogEntry.OVERRIDE )
1201            {
1202                over = e.yes_or_no().equalsIgnoreCase( "YES" );
1203                continue;
1204            }
1205
1206            if( e.entryType() == CatalogEntry.PUBLIC
1207                 && e.publicId().equals( publicId ) )
1208            {
1209                if( over || systemId == null )
1210                {
1211                    return e.formalSystemIdentifier();
1212                }
1213            }
1214        }
1215
1216        // If there's a DELEGATE entry in this catalog, use it
1217
over = default_override;
1218        enum = catalogEntries.elements();
1219        Vector delCats = new Vector();
1220        while( enum.hasMoreElements() )
1221        {
1222            CatalogEntry e = (CatalogEntry)enum.nextElement();
1223            if( e.entryType() == CatalogEntry.OVERRIDE )
1224            {
1225                over = e.yes_or_no().equalsIgnoreCase( "YES" );
1226                continue;
1227            }
1228
1229            if( e.entryType() == CatalogEntry.DELEGATE
1230                 && ( over || systemId == null ) )
1231            {
1232                String p = (String)e.partialPublicId();
1233                if( p.length() <= publicId.length()
1234                     && p.equals( publicId.substring( 0, p.length() ) ) )
1235                {
1236                    // delegate this match to the other catalog
1237

1238                    delCats.addElement( e.formalSystemIdentifier() );
1239                }
1240            }
1241        }
1242
1243        if( delCats.size() > 0 )
1244        {
1245            Enumeration enumCats = delCats.elements();
1246
1247            if( debug > 0 )
1248            {
1249                debug( 1, "Switching to delegated catalog(s):" );
1250                while( enumCats.hasMoreElements() )
1251                {
1252                    String delegatedCatalog = (String)enumCats.nextElement();
1253                    debug( 1, "\t" + delegatedCatalog );
1254                }
1255            }
1256
1257            Catalog dcat = new Catalog();
1258            dcat.setParserClass( parserClass );
1259            dcat.debug = debug;
1260
1261            enumCats = delCats.elements();
1262            while( enumCats.hasMoreElements() )
1263            {
1264                String delegatedCatalog = (String)enumCats.nextElement();
1265                dcat.parseCatalog( delegatedCatalog );
1266            }
1267
1268            return dcat.resolvePublic( publicId, null );
1269        }
1270
1271        // Nada!
1272
return null;
1273    }
1274
1275    /**
1276     * <p>
1277     *
1278     * Return the applicable SYSTEM system identifier in this catalog.</p> <p>
1279     *
1280     * If a SYSTEM entry exists in the catalog file for the system ID specified,
1281     * return the mapped value.</p>
1282     *
1283     * @param systemId The system ID to locate in the catalog
1284     * @return The mapped system identifier or null
1285     */

1286    private String resolveLocalSystem( String systemId )
1287    {
1288        String osname = System.getProperty( "os.name" );
1289        boolean windows = ( osname.indexOf( "Windows" ) >= 0 );
1290        Enumeration enum = catalogEntries.elements();
1291        while( enum.hasMoreElements() )
1292        {
1293            CatalogEntry e = (CatalogEntry)enum.nextElement();
1294            if( e.entryType() == CatalogEntry.SYSTEM
1295                 && ( e.systemId().equals( systemId )
1296                 || ( windows
1297                 && e.systemId().equalsIgnoreCase( systemId ) ) ) )
1298            {
1299                return e.formalSystemIdentifier();
1300            }
1301        }
1302        return null;
1303    }
1304
1305
1306    /**
1307     * <p>
1308     *
1309     * Search the subordinate catalogs, in order, looking for a match.</p> <p>
1310     *
1311     * This method searches the Catalog and returns the system identifier
1312     * specified for the given entity type with the given name, public, and
1313     * system identifiers. In some contexts, these may be null.</p>
1314     *
1315     * @param entityType The CatalogEntry type for which this query is being
1316     * conducted. This is necessary in order to do the approprate query on
1317     * a subordinate catalog.
1318     * @param entityName The name of the entity being searched for, if
1319     * appropriate.
1320     * @param publicId The public identifier of the entity in question (as
1321     * provided in the source document).
1322     * @param systemId The nominal system identifier for the entity in question
1323     * (as provided in the source document).
1324     * @return The system identifier to use. Note that the nominal system
1325     * identifier is not returned if a match is not found in the catalog,
1326     * instead null is returned to indicate that no match was found.
1327     * @throws MalformedURLException The formal system identifier of a delegated
1328     * catalog cannot be turned into a valid URL.
1329     * @throws IOException Error reading delegated catalog file.
1330     */

1331    private synchronized String resolveSubordinateCatalogs( int entityType,
1332                                                            String entityName,
1333                                                            String publicId,
1334                                                            String systemId )
1335        throws MalformedURLException, IOException
1336    {
1337
1338        for( int catPos = 0; catPos < catalogs.size(); catPos++ )
1339        {
1340            Catalog c = null;
1341
1342            try
1343            {
1344                c = (Catalog)catalogs.elementAt( catPos );
1345            }
1346            catch( ClassCastException e )
1347            {
1348                String catfile = (String)catalogs.elementAt( catPos );
1349                c = new Catalog();
1350                c.setParserClass( parserClass );
1351                c.debug = debug;
1352
1353                try
1354                {
1355                    c.parseCatalog( catfile );
1356                }
1357                catch( MalformedURLException mue )
1358                {
1359                    debug( 1, "Malformed Catalog URL", catfile );
1360                }
1361                catch( FileNotFoundException fnfe )
1362                {
1363                    debug( 1, "Failed to load catalog, file not found",
1364                        catfile );
1365                }
1366                catch( IOException ioe )
1367                {
1368                    debug( 1, "Failed to load catalog, I/O error", catfile );
1369                }
1370
1371                catalogs.setElementAt( c, catPos );
1372            }
1373
1374            String resolved = null;
1375
1376            // Ok, now what are we supposed to call here?
1377
switch ( entityType )
1378            {
1379                case CatalogEntry.DOCTYPE:
1380                {
1381                    resolved = c.resolveDoctype( entityName,
1382                        publicId,
1383                        systemId );
1384                    break;
1385                }
1386                case CatalogEntry.DOCUMENT:
1387                {
1388                    resolved = c.resolveDocument();
1389                    break;
1390                }
1391                case CatalogEntry.ENTITY:
1392                {
1393                    resolved = c.resolveEntity( entityName,
1394                        publicId,
1395                        systemId );
1396                    break;
1397                }
1398                case CatalogEntry.NOTATION:
1399                {
1400                    resolved = c.resolveNotation( entityName,
1401                        publicId,
1402                        systemId );
1403                    break;
1404                }
1405                case CatalogEntry.PUBLIC:
1406                {
1407                    resolved = c.resolvePublic( publicId, systemId );
1408                    break;
1409                }
1410                case CatalogEntry.SYSTEM:
1411                {
1412                    resolved = c.resolveSystem( systemId );
1413                    break;
1414                }
1415            }
1416
1417            if( resolved != null )
1418            {
1419                return resolved;
1420            }
1421        }
1422
1423        return null;
1424    }
1425
1426    // -----------------------------------------------------------------
1427

1428    /**
1429     * <p>
1430     *
1431     * Replace backslashes with forward slashes. (URLs always use forward
1432     * slashes.)</p>
1433     *
1434     * @param sysid The input system identifier.
1435     * @return The same system identifier with backslashes turned into forward
1436     * slashes.
1437     */

1438    private String fixSlashes( String sysid )
1439    {
1440        return sysid.replace( '\\', '/' );
1441    }
1442
1443    /**
1444     * <p>
1445     *
1446     * Construct an absolute URI from a relative one, using the current base
1447     * URI.</p>
1448     *
1449     * @param sysid The (possibly relative) system identifier
1450     * @return The system identifier made absolute with respect to the current
1451     * {@link #base}.
1452     */

1453    private String makeAbsolute( String sysid )
1454    {
1455        URL local = null;
1456
1457        sysid = fixSlashes( sysid );
1458
1459        try
1460        {
1461            local = new URL( base, sysid );
1462        }
1463        catch( MalformedURLException e )
1464        {
1465            debug( 1, "Malformed URL on system identifier", sysid );
1466        }
1467
1468        if( local != null )
1469        {
1470            return local.toString();
1471        }
1472        else
1473        {
1474            return sysid;
1475        }
1476    }
1477
1478    /**
1479     * <p>
1480     *
1481     * Print debug message (if the debug level is high enough).</p>
1482     *
1483     * @param level The debug level of this message. This message will only be
1484     * displayed if the current debug level is at least equal to this
1485     * value.
1486     * @param message The text of the message.
1487     */

1488    private void debug( int level, String message )
1489    {
1490        if( debug >= level )
1491        {
1492            System.out.println( message );
1493        }
1494    }
1495
1496    /**
1497     * <p>
1498     *
1499     * Print debug message (if the debug level is high enough).</p>
1500     *
1501     * @param level The debug level of this message. This message will only be
1502     * displayed if the current debug level is at least equal to this
1503     * value.
1504     * @param message The text of the message.
1505     * @param spec An argument to the message.
1506     */

1507    private void debug( int level, String message, String spec )
1508    {
1509        if( debug >= level )
1510        {
1511            System.out.println( new StringBuffer(message).append(": ").append(spec) );
1512        }
1513    }
1514
1515    /**
1516     * <p>
1517     *
1518     * Print debug message (if the debug level is high enough).</p>
1519     *
1520     * @param level The debug level of this message. This message will only be
1521     * displayed if the current debug level is at least equal to this
1522     * value.
1523     * @param message The text of the message.
1524     * @param spec1 An argument to the message.
1525     * @param spec2 DOC: Insert Description of Parameter
1526     */

1527    private void debug( int level, String message, String spec1, String spec2 )
1528    {
1529        if( debug >= level )
1530        {
1531            System.out.println( new StringBuffer(message).append(": ").append(spec1) );
1532            System.out.println( "\t" + spec2 );
1533        }
1534    }
1535
1536    // -----------------------------------------------------------------
1537

1538    /**
1539     * <p>
1540     *
1541     * Add to the current list of delegated catalogs.</p> <p>
1542     *
1543     * This method always constructs the {@link #localDelegate} vector so that
1544     * it is ordered by length of partial public identifier.</p>
1545     *
1546     * @param entry The DELEGATE catalog entry
1547     */

1548    private void addDelegate( CatalogEntry entry )
1549    {
1550        int pos = 0;
1551        String partial = entry.partialPublicId();
1552
1553        Enumeration local = localDelegate.elements();
1554        while( local.hasMoreElements() )
1555        {
1556            CatalogEntry dpe = (CatalogEntry)local.nextElement();
1557            String dp = dpe.partialPublicId();
1558            if( dp.equals( partial ) )
1559            {
1560                // we already have this prefix
1561
return;
1562            }
1563            if( dp.length() > partial.length() )
1564            {
1565                pos++;
1566            }
1567            if( dp.length() < partial.length() )
1568            {
1569                break;
1570            }
1571        }
1572
1573        // now insert partial into the vector at [pos]
1574
if( localDelegate.size() == 0 )
1575        {
1576            localDelegate.addElement( entry );
1577        }
1578        else
1579        {
1580            localDelegate.insertElementAt( entry, pos );
1581        }
1582    }
1583}
1584
Popular Tags