Project

General

Profile

1
// Catalog.java - Represents OASIS Open Catalog files.
2

    
3
// Written by Norman Walsh, nwalsh@arbortext.com
4
// NO WARRANTY! This class is in the public domain.
5

    
6
package com.arbortext.catalog;
7

    
8
import java.lang.Integer;
9
import java.io.IOException;
10
import java.io.FileNotFoundException;
11
import java.util.Enumeration;
12
import java.util.Hashtable;
13
import java.util.Vector;
14
import java.net.URL;
15
import java.net.MalformedURLException;
16
import com.arbortext.catalog.CatalogReader;
17
import com.arbortext.catalog.XMLCatalogReader;
18
import com.arbortext.catalog.NotXMLCatalogException;
19
import com.arbortext.catalog.NoXMLParserException;
20
import org.xml.sax.SAXException;
21

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

    
155
    /**
156
     * The base URI of the Catalog file currently being parsed.
157
     */
158
    private URL catalogCwd;
159

    
160
    /** The catalog entries currently known to the system. */
161
    private Vector catalogEntries = new Vector();
162

    
163
    /** The default initial override setting. */
164
    private boolean default_override = true;
165

    
166
    /**
167
     * <p>The debug level.</p>
168
     *
169
     * <p>In general, higher numbers produce more information:</p>
170
     * <ul>
171
     * <li>0, no messages
172
     * <li>1, minimal messages (high-level status)
173
     * <li>2, more messages
174
     * <li>3, detailed messages
175
     * </ul>
176
     */
177
    public int debug = 0;
178

    
179
    /**
180
     * <p>A vector of catalog files to be loaded.</p>
181
     *
182
     * <p>This list is initially established by
183
     * <code>loadSystemCatalogs</code> when
184
     * it parses the system catalog list, but CATALOG entries may
185
     * contribute to it during the course of parsing.</p>
186
     *
187
     * @see #loadSystemCatalogs
188
     * @see localCatalogFiles
189
     */
190
    private Vector catalogFiles = new Vector();
191

    
192
    /**
193
     * <p>A vector of catalog files constructed during processing of
194
     * CATALOG entries in the current catalog.</p>
195
     *
196
     * <p>This two-level system is actually necessary to correctly implement
197
     * the semantics of the CATALOG entry. If one catalog file includes
198
     * another with a CATALOG entry, the included catalog logically
199
     * occurs <i>at the end</i> of the including catalog, and after any
200
     * preceding CATALOG entries. In other words, the CATALOG entry
201
     * cannot insert anything into the middle of a catalog file.</p>
202
     *
203
     * <p>When processing reaches the end of each catalog files, any
204
     * elements on this vector are added to the front of the
205
     * <code>catalogFiles</code> vector.</p>
206
     *
207
     * @see catalogFiles
208
     */
209
    private Vector localCatalogFiles = new Vector();
210

    
211
    /**
212
     * <p>A vector of Catalogs.</p>
213
     *
214
     * <p>The semantics of Catalog resolution are such that each
215
     * catalog is effectively a list of Catalogs (in other words,
216
     * a recursive list of Catalog instances).</p>
217
     *
218
     * <p>Catalogs that are processed as the result of CATALOG or
219
     * DELEGATE entries are subordinate to the catalog that contained
220
     * them, but they may in turn have subordinate catalogs.</p>
221
     *
222
     * <p>Catalogs are only loaded when they are needed, so this vector
223
     * initially contains a list of Catalog filenames (URLs). If, during
224
     * processing, one of these catalogs has to be loaded, the resulting
225
     * Catalog object is placed in the vector, effectively caching it
226
     * for the next query.</p>
227
     */
228
    private Vector catalogs = new Vector();
229

    
230
    /**
231
     * <p>A vector of DELEGATE Catalog entries constructed during
232
     * processing of the Catalog.</p>
233
     *
234
     * <p>This two-level system has two purposes; first, it allows
235
     * us to sort the DELEGATE entries by the length of the partial
236
     * public identifier so that a linear search encounters them in
237
     * the correct order and second, it puts them all at the end of
238
     * the Catalog.</p>
239
     *
240
     * <p>When processing reaches the end of each catalog file, any
241
     * elements on this vector are added to the end of the
242
     * <code>catalogEntries</code> vector. This assures that matching
243
     * PUBLIC keywords are encountered before DELEGATE entries.</p>
244
     */
245
    private Vector localDelegate = new Vector();
246

    
247
    /**
248
     * <p>The name of the parser class to load when parsing XML Catalogs.</p>
249
     *
250
     * <p>If a parser class is provided,
251
     * subsequent attempts to parse Catalog files will begin
252
     * by attemptiing an XML parse of the catalog file using a parser
253
     * of this class.
254
     * If the XML parse fails, the "default" text parse will be done
255
     * instead.</p>
256
     */
257
    private String parserClass = null;
258

    
259
    /**
260
     * <p>Constructs an empty Catalog.</p>
261
     *
262
     * <p>The constructor interrogates the relevant system properties
263
     * and initializes the catalog data structures.</p>
264
     */
265
    public Catalog() {
266
	String property = System.getProperty("xml.catalog.debug");
267

    
268
	if (property != null) {
269
	    try {
270
		debug = Integer.parseInt(property);
271
	    } catch (NumberFormatException e) {
272
		debug = 0;
273
	    }
274
	}
275

    
276
	property = System.getProperty("xml.catalog.override");
277

    
278
	if (property != null) {
279
	    default_override = (property.equalsIgnoreCase("true")
280
				|| property.equalsIgnoreCase("yes")
281
				|| property.equalsIgnoreCase("1"));
282
	}
283
    }
284

    
285
    /**
286
     * <p>Sets the parser class, enabling XML Catalog parsing.</p>
287
     *
288
     * <p>Sets the parser class that will be used for loading XML Catalogs.
289
     * If this method is not called, all catalogs will be parsed as
290
     * plain text (and assumed to conform to the
291
     * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Catalog
292
     * format</a>).</p>
293
     *
294
     * @param parser The name of a class implementing the SAX Parser
295
     * interface to be used for subsequent XML Catalog parsing.
296
     */
297
    public void setParserClass(String parser) {
298
	parserClass = parser;
299
    }
300

    
301
    /**
302
     * <p>Load the system catalog files.</p>
303
     *
304
     * <p>The method adds all of the
305
     * catalogs specified in the <tt>xml.catalog.files</tt> property
306
     * to the Catalog list.</p>
307
     *
308
     * @throws MalformedURLException  One of the system catalogs is
309
     * identified with a filename that is not a valid URL.
310
     * @throws IOException One of the system catalogs cannot be read.
311
     */
312
    public void loadSystemCatalogs()
313
	throws MalformedURLException, IOException {
314
	String PCS = System.getProperty("path.separator");
315
	String catalog_files = System.getProperty("xml.catalog.files");
316

    
317
	while (catalog_files != null) {
318
	    int pos = catalog_files.indexOf(PCS);
319
	    String catfile = null;
320

    
321
	    if (pos > 0) {
322
		catfile = catalog_files.substring(0, pos);
323
		catalog_files = catalog_files.substring(pos+1);
324
	    } else {
325
		catfile = catalog_files;
326
		catalog_files = null;
327
	    }
328

    
329
	    catalogFiles.addElement(catfile);
330
	}
331

    
332
	if (catalogFiles.size() > 0) {
333
	    // This is a little odd. The parseCatalog() method expects
334
	    // a filename, but it adds that name to the end of the
335
	    // catalogFiles vector, and then processes that vector.
336
	    // This allows the system to handle CATALOG entries
337
	    // correctly.
338
	    //
339
	    // In this init case, we take the last element off the
340
	    // catalogFiles vector and pass it to parseCatalog. This
341
	    // will "do the right thing" in the init case, and allow
342
	    // parseCatalog() to do the right thing in the non-init
343
	    // case. Honest.
344
	    //
345
	    String catfile = (String) catalogFiles.lastElement();
346
	    catalogFiles.removeElement(catfile);
347
	    parseCatalog(catfile);
348
	}
349
    }
350

    
351
    /**
352
     * <p>Parse a catalog file, augmenting internal data structures</p>
353
     *
354
     * @param fileName The filename of the catalog file to process
355
     *
356
     * @throws MalformedURLException The fileName cannot be turned into
357
     * a valid URL.
358
     * @throws IOException Error reading catalog file.
359
     */
360
    public synchronized void parseCatalog(String fileName)
361
	throws MalformedURLException, IOException {
362

    
363
	// Put the file into the list of catalogs to process...
364
	// In all cases except the case when initCatalog() is the
365
	// caller, this will be the only catalog initially in the list...
366
	catalogFiles.addElement(fileName);
367

    
368
	// Now process all the files on the catalogFiles vector. This
369
	// vector can grow during processing if CATALOG entries are
370
	// encountered in the catalog
371
	int curCat = 0;
372
	while (curCat < catalogFiles.size()) {
373
	    String catfile = (String) catalogFiles.elementAt(curCat++);
374

    
375
	    if (catalogEntries.size() == 0 && catalogs.size() == 0) {
376
		// We haven't parsed any catalogs yet, let this
377
		// catalog be the first...
378
		parseCatalogFile(catfile);
379
	    } else {
380
		// This is a subordinate catalog. We save its name,
381
		// but don't bother to load it unless it's necessary.
382
		catalogs.addElement(catfile);
383
	    }
384

    
385
	    if (!localCatalogFiles.isEmpty()) {
386
		// Move all the localCatalogFiles into the front of
387
		// the catalogFiles queue
388
		Vector newQueue = new Vector();
389
		Enumeration q = localCatalogFiles.elements();
390
		while (q.hasMoreElements()) {
391
		    newQueue.addElement(q.nextElement());
392
		}
393

    
394
		// Put the rest of the catalogs on the end of the new list
395
		while (curCat < catalogFiles.size()) {
396
		    catfile = (String) catalogFiles.elementAt(curCat++);
397
		    newQueue.addElement(catfile);
398
		}
399

    
400
		localCatalogFiles = new Vector();
401
		catalogFiles = newQueue;
402
		curCat = 0;
403
	    }
404

    
405
	    if (!localDelegate.isEmpty()) {
406
		Enumeration e = localDelegate.elements();
407
		while (e.hasMoreElements()) {
408
		    catalogEntries.addElement(e.nextElement());
409
		}
410
		localDelegate = new Vector();
411
	    }
412
	}
413

    
414
	// We've parsed them all, reinit the vector...
415
	catalogFiles = new Vector();
416
    }
417

    
418
    /**
419
     * <p>Parse a single catalog file, augmenting internal data structures</p>
420
     *
421
     * @param fileName The filename of the catalog file to process
422
     *
423
     * @throws MalformedURLException The fileName cannot be turned into
424
     * a valid URL.
425
     * @throws IOException Error reading catalog file.
426
     */
427
    private synchronized void parseCatalogFile(String fileName)
428
	throws MalformedURLException, IOException {
429

    
430
	CatalogEntry entry;
431

    
432
	// The base-base is the cwd. If the catalog file is specified
433
	// with a relative path, this assures that it gets resolved
434
	// properly...
435
	try {
436
	    // tack on a basename because URLs point to files not dirs
437
	    String userdir = fixSlashes(System.getProperty("user.dir"));
438
	    catalogCwd = new URL("file:///" + userdir + "/basename");
439
	} catch (MalformedURLException e) {
440
	    String userdir = fixSlashes(System.getProperty("user.dir"));
441
	    debug(1, "Malformed URL on cwd", userdir);
442
	    catalogCwd = null;
443
	}
444

    
445
	// The initial base URI is the location of the catalog file
446
	try {
447
	    base = new URL(catalogCwd, fixSlashes(fileName));
448
	} catch (MalformedURLException e) {
449
	    try {
450
		base = new URL("file:///" + fixSlashes(fileName));
451
	    } catch (MalformedURLException e2) {
452
		debug(1, "Malformed URL on catalog filename", 
453
		      fixSlashes(fileName));
454
		base = null;
455
	    }
456
	}
457

    
458
	debug(1, "Loading catalog", fileName);
459
	debug(3, "Default BASE", base.toString());
460

    
461
	fileName = base.toString();
462

    
463
	if (parserClass != null) {
464
	    try {
465
		XMLCatalogReader catfile = new XMLCatalogReader();
466
		catfile.setParserClass(parserClass);
467
		catfile.parseCatalog(fileName);
468

    
469
		CatalogEntry ce = null;
470
		while ((ce = catfile.nextEntry()) != null) {
471
		    addEntry(ce);
472
		}
473
		return;
474
	    } catch (SAXException e1) {
475
		// not an XML catalog, continue with text parse
476
	    } catch (NoXMLParserException e2) {
477
		// not an XML catalog, continue with text parse
478
	    } catch (NotXMLCatalogException e2) {
479
		// not an XML catalog, continue with text parse
480
	    } catch (InstantiationException e3) {
481
		debug(1, "Cannot instantiate XML Parser class", parserClass);
482
	    } catch (IllegalAccessException e4) {
483
		debug(1, "Cannot access XML Parser class", parserClass);
484
	    } catch (ClassNotFoundException e5) {
485
		debug(1, "Cannot load XML Parser class", parserClass);
486
	    } catch (UnknownCatalogFormatException e6) {
487
		debug(1, "Unrecognized XML Catalog format.");
488
		return;
489
	    }
490
	}
491

    
492
	CatalogReader catfile = new CatalogReader();
493
	catfile.parseCatalog(fileName);
494

    
495
	// Process the contents of the catalog file as a whitespace
496
	// delimited set of tokens
497
	while ((entry = catfile.nextEntry()) != null) {
498
	    addEntry(entry);
499
	}
500
    }
501

    
502
    /**
503
     * <p>Cleanup and process a Catalog entry.</p>
504
     *
505
     * <p>This method processes each Catalog entry, changing mapped
506
     * relative system identifiers into absolute ones (based on the current
507
     * base URI), and maintaining other information about the current
508
     * catalog.</p>
509
     *
510
     * @param entry The CatalogEntry to process.
511
     */
512
    private void addEntry(CatalogEntry entry) {
513
	switch (entry.entryType()) {
514
	case CatalogEntry.BASE: {
515
	    String value = entry.formalSystemIdentifier();
516
	    URL newbase = null;
517

    
518
	    debug(3, "BASE", value);
519

    
520
	    try {
521
		value = fixSlashes(value);
522
		newbase = new URL(catalogCwd, value);
523
	    } catch (MalformedURLException e) {
524
		try {
525
		    newbase = new URL("file:///" + value);
526
		} catch (MalformedURLException e2) {
527
		    debug(1, "Malformed URL on base", value);
528
		    newbase = null;
529
		}
530
	    }
531

    
532
	    if (newbase != null) {
533
		base = newbase;
534
	    }
535

    
536
	    break;
537
	}
538

    
539
	case CatalogEntry.CATALOG: {
540
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
541

    
542
	    debug(3, "CATALOG", fsi);
543

    
544
	    localCatalogFiles.addElement(fsi);
545
	    break;
546
	}
547

    
548
	case CatalogEntry.DOCUMENT: {
549
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
550
	    entry.updateFormalSystemIdentifier(fsi);
551

    
552
	    debug(3, "DOCUMENT", fsi);
553

    
554
	    catalogEntries.addElement(entry);
555
	    break;
556
	}
557
	case CatalogEntry.OVERRIDE: {
558
	    debug(3, "OVERRIDE", entry.yes_or_no());
559

    
560
	    catalogEntries.addElement(entry);
561
	    break;
562
	}
563
	case CatalogEntry.SGMLDECL: {
564
	    // meaningless in XML
565
	    break;
566
	}
567
	case CatalogEntry.DELEGATE: {
568
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
569
	    entry.updateFormalSystemIdentifier(fsi);
570

    
571
	    debug(3, "DELEGATE", entry.partialPublicId(), fsi);
572

    
573
	    addDelegate(entry);
574
	    break;
575
	}
576
	case CatalogEntry.DOCTYPE: {
577
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
578
	    entry.updateFormalSystemIdentifier(fsi);
579

    
580
	    debug(3, "DOCTYPE", entry.publicId(), fsi);
581

    
582
	    catalogEntries.addElement(entry);
583
	    break;
584
	}
585
	case CatalogEntry.DTDDECL: {
586
	    // meaningless in XML
587
	    break;
588
	}
589
	case CatalogEntry.ENTITY: {
590
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
591
	    entry.updateFormalSystemIdentifier(fsi);
592

    
593
	    debug(3, "ENTITY", entry.entityName(), fsi);
594

    
595
	    catalogEntries.addElement(entry);
596
	    break;
597
	}
598
	case CatalogEntry.LINKTYPE: {
599
	    // meaningless in XML
600
	    break;
601
	}
602
	case CatalogEntry.NOTATION: {
603
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
604
	    entry.updateFormalSystemIdentifier(fsi);
605

    
606
	    debug(3, "NOTATION", entry.entityName(), fsi);
607

    
608
	    catalogEntries.addElement(entry);
609
	    break;
610
	}
611
	case CatalogEntry.PUBLIC: {
612
	    // This entry has to go in the vector because it would
613
	    // be relevant in subsequent searches for notations.
614
	    String publicid = entry.publicId();
615
	    String systemid = makeAbsolute(entry.formalSystemIdentifier());
616

    
617
	    debug(3, "PUBLIC", publicid, systemid);
618

    
619
	    entry.updateFormalSystemIdentifier(systemid);
620
	    catalogEntries.addElement(entry);
621
	    break;
622
	}
623
	case CatalogEntry.SYSTEM: {
624
	    String systemid = entry.systemId();
625
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
626

    
627
	    debug(3, "SYSTEM", systemid, fsi);
628

    
629
	    entry.updateFormalSystemIdentifier(fsi);
630
	    catalogEntries.addElement(entry);
631
	    break;
632
	}
633
	}
634
    }
635

    
636
    /**
637
     * <p>Parse all subordinate catalogs.</p>
638
     *
639
     * <p>This method recursively parses all of the subordinate catalogs.
640
     * If this method does not throw an exception, you can be confident that
641
     * no subsequent call to any resolve*() method will either, with two
642
     * possible exceptions:</p>
643
     *
644
     * <ol>
645
     * <li><p>Delegated catalogs are re-parsed each time they are needed
646
     * (because a variable list of them may be needed in each case,
647
     * depending on the length of the matching partial public identifier).</p>
648
     * <p>But they are parsed by this method, so as long as they don't
649
     * change or disappear while the program is running, they shouldn't
650
     * generate errors later if they don't generate errors now.</p>
651
     * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
652
     * won't be loaded until they are needed or until you call
653
     * <code>parseAllCatalogs</code> again.</p>
654
     * </ol>
655
     *
656
     * <p>On the other hand, if you don't call this method, you may
657
     * successfully parse documents without having to load all possible
658
     * catalogs.</p>
659
     *
660
     * @throws MalformedURLException The filename (URL) for a
661
     * subordinate or delegated catalog is not a valid URL.
662
     * @throws IOException Error reading some subordinate or delegated
663
     * catalog file.
664
     */
665
    public void parseAllCatalogs()
666
	throws MalformedURLException, IOException {
667

    
668
	// Parse all the subordinate catalogs
669
	for (int catPos = 0; catPos < catalogs.size(); catPos++) {
670
	    Catalog c = null;
671

    
672
	    try {
673
		c = (Catalog) catalogs.elementAt(catPos);
674
	    } catch (ClassCastException e) {
675
		String catfile = (String) catalogs.elementAt(catPos);
676
		c = new Catalog();
677
		c.setParserClass(parserClass);
678
		c.debug = debug;
679

    
680
		c.parseCatalog(catfile);
681
		catalogs.setElementAt(c, catPos);
682
		c.parseAllCatalogs();
683
	    }
684
	}
685

    
686
	// Parse all the DELEGATE catalogs
687
	Enumeration enum = catalogEntries.elements();
688
	while (enum.hasMoreElements()) {
689
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
690
	    if (e.entryType() == CatalogEntry.DELEGATE) {
691
		Catalog dcat = new Catalog();
692
		dcat.setParserClass(parserClass);
693
		dcat.debug = debug;
694
		dcat.parseCatalog(e.formalSystemIdentifier());
695
	    }
696
	}
697
    }
698

    
699

    
700
    /**
701
     * <p>Return the applicable DOCTYPE system identifier.</p>
702
     *
703
     * @param entityName The name of the entity (element) for which
704
     * a doctype is required.
705
     * @param publicId The nominal public identifier for the doctype
706
     * (as provided in the source document).
707
     * @param systemId The nominal system identifier for the doctype
708
     * (as provided in the source document).
709
     *
710
     * @return The system identifier to use for the doctype.
711
     *
712
     * @throws MalformedURLException The formal system identifier of a
713
     * subordinate catalog cannot be turned into a valid URL.
714
     * @throws IOException Error reading subordinate catalog file.
715
     */
716
    public String resolveDoctype(String entityName,
717
				 String publicId,
718
				 String systemId)
719
	throws MalformedURLException, IOException {
720
	String resolved = null;
721

    
722
	if (systemId != null) {
723
	    // If there's a SYSTEM entry in this catalog, use it
724
	    resolved = resolveLocalSystem(systemId);
725
	    if (resolved != null) {
726
		return resolved;
727
	    }
728
	}
729

    
730
	if (publicId != null) {
731
	    // If there's a PUBLIC entry in this catalog, use it
732
	    resolved = resolveLocalPublic(CatalogEntry.DOCTYPE,
733
					  entityName,
734
					  publicId,
735
					  systemId);
736
	    if (resolved != null) {
737
		return resolved;
738
	    }
739
	}
740

    
741
	// If there's a DOCTYPE entry in this catalog, use it
742
	boolean over = default_override;
743
	Enumeration enum = catalogEntries.elements();
744
	while (enum.hasMoreElements()) {
745
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
746
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
747
		over = e.yes_or_no().equalsIgnoreCase("YES");
748
		continue;
749
	    }
750

    
751
	    if (e.entryType() == CatalogEntry.DOCTYPE
752
		&& e.entityName().equals(entityName)) {
753
		if (over || systemId == null) {
754
		    return e.formalSystemIdentifier();
755
		}
756
	    }
757
	}
758

    
759
	// Otherwise, look in the subordinate catalogs
760
	return resolveSubordinateCatalogs(CatalogEntry.DOCTYPE,
761
					  entityName,
762
					  publicId,
763
					  systemId);
764
    }
765

    
766
    /**
767
     * <p>Return the applicable DOCUMENT entry.</p>
768
     *
769
     * @return The system identifier to use for the doctype.
770
     *
771
     * @throws MalformedURLException The formal system identifier of a
772
     * subordinate catalog cannot be turned into a valid URL.
773
     * @throws IOException Error reading subordinate catalog file.
774
     */
775
    public String resolveDocument()
776
	throws MalformedURLException, IOException {
777
	// If there's a DOCUMENT entry, return it
778
	Enumeration enum = catalogEntries.elements();
779
	while (enum.hasMoreElements()) {
780
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
781
	    if (e.entryType() == CatalogEntry.DOCUMENT) {
782
		return e.formalSystemIdentifier();
783
	    }
784
	}
785

    
786
	return resolveSubordinateCatalogs(CatalogEntry.DOCUMENT,
787
					  null, null, null);
788
    }
789

    
790
    /**
791
     * <p>Return the applicable ENTITY system identifier.</p>
792
     *
793
     * @param entityName The name of the entity for which
794
     * a system identifier is required.
795
     * @param publicId The nominal public identifier for the entity
796
     * (as provided in the source document).
797
     * @param systemId The nominal system identifier for the entity
798
     * (as provided in the source document).
799
     *
800
     * @return The system identifier to use for the entity.
801
     *
802
     * @throws MalformedURLException The formal system identifier of a
803
     * subordinate catalog cannot be turned into a valid URL.
804
     * @throws IOException Error reading subordinate catalog file.
805
     */
806
    public String resolveEntity(String entityName,
807
				String publicId,
808
				String systemId)
809
	throws MalformedURLException, IOException {
810
	String resolved = null;
811

    
812
	if (systemId != null) {
813
	    // If there's a SYSTEM entry in this catalog, use it
814
	    resolved = resolveLocalSystem(systemId);
815
	    if (resolved != null) {
816
		return resolved;
817
	    }
818
	}
819

    
820
	if (publicId != null) {
821
	    // If there's a PUBLIC entry in this catalog, use it
822
	    resolved = resolveLocalPublic(CatalogEntry.ENTITY,
823
					  entityName,
824
					  publicId,
825
					  systemId);
826
	    if (resolved != null) {
827
		return resolved;
828
	    }
829
	}
830

    
831
	// If there's a ENTITY entry in this catalog, use it
832
	boolean over = default_override;
833
	Enumeration enum = catalogEntries.elements();
834
	while (enum.hasMoreElements()) {
835
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
836
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
837
		over = e.yes_or_no().equalsIgnoreCase("YES");
838
		continue;
839
	    }
840

    
841
	    if (e.entryType() == CatalogEntry.ENTITY
842
		&& e.entityName().equals(entityName)) {
843
		if (over || systemId == null) {
844
		    return e.formalSystemIdentifier();
845
		}
846
	    }
847
	}
848

    
849
	// Otherwise, look in the subordinate catalogs
850
	return resolveSubordinateCatalogs(CatalogEntry.ENTITY,
851
					  entityName,
852
					  publicId,
853
					  systemId);
854
    }
855

    
856
    /**
857
     * <p>Return the applicable NOTATION system identifier.</p>
858
     *
859
     * @param notationName The name of the notation for which
860
     * a doctype is required.
861
     * @param publicId The nominal public identifier for the notation
862
     * (as provided in the source document).
863
     * @param systemId The nominal system identifier for the notation
864
     * (as provided in the source document).
865
     *
866
     * @return The system identifier to use for the notation.
867
     *
868
     * @throws MalformedURLException The formal system identifier of a
869
     * subordinate catalog cannot be turned into a valid URL.
870
     * @throws IOException Error reading subordinate catalog file.
871
     */
872
    public String resolveNotation(String notationName,
873
				  String publicId,
874
				  String systemId)
875
	throws MalformedURLException, IOException {
876
	String resolved = null;
877

    
878
	if (systemId != null) {
879
	    // If there's a SYSTEM entry in this catalog, use it
880
	    resolved = resolveLocalSystem(systemId);
881
	    if (resolved != null) {
882
		return resolved;
883
	    }
884
	}
885

    
886
	if (publicId != null) {
887
	    // If there's a PUBLIC entry in this catalog, use it
888
	    resolved = resolveLocalPublic(CatalogEntry.NOTATION,
889
					  notationName,
890
					  publicId,
891
					  systemId);
892
	    if (resolved != null) {
893
		return resolved;
894
	    }
895
	}
896

    
897
	// If there's a NOTATION entry in this catalog, use it
898
	boolean over = default_override;
899
	Enumeration enum = catalogEntries.elements();
900
	while (enum.hasMoreElements()) {
901
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
902
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
903
		over = e.yes_or_no().equalsIgnoreCase("YES");
904
		continue;
905
	    }
906

    
907
	    if (e.entryType() == CatalogEntry.NOTATION
908
		&& e.entityName().equals(notationName)) {
909
		if (over || systemId == null) {
910
		    return e.formalSystemIdentifier();
911
		}
912
	    }
913
	}
914

    
915
	// Otherwise, look in the subordinate catalogs
916
	return resolveSubordinateCatalogs(CatalogEntry.NOTATION,
917
					  notationName,
918
					  publicId,
919
					  systemId);
920
    }
921

    
922
    /**
923
     * <p>Return the applicable PUBLIC or SYSTEM identifier.</p>
924
     *
925
     * <p>This method searches the Catalog and returns the system
926
     * identifier specified for the given system or
927
     * public identifiers. If
928
     * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
929
     * null is returned.</p>
930
     *
931
     * @param publicId The public identifier to locate in the catalog.
932
     * Public identifiers are normalized before comparison.
933
     * @param systemId The nominal system identifier for the entity
934
     * in question (as provided in the source document).
935
     *
936
     * @throws MalformedURLException The formal system identifier of a
937
     * subordinate catalog cannot be turned into a valid URL.
938
     * @throws IOException Error reading subordinate catalog file.
939
     *
940
     * @return The system identifier to use.
941
     * Note that the nominal system identifier is not returned if a
942
     * match is not found in the catalog, instead null is returned
943
     * to indicate that no match was found.
944
     */
945
    public String resolvePublic(String publicId, String systemId) 
946
	throws MalformedURLException, IOException {
947

    
948
	// If there's a SYSTEM entry in this catalog, use it
949
	if (systemId != null) {
950
	    String resolved = resolveLocalSystem(systemId);
951
	    if (resolved != null) {
952
		return resolved;
953
	    }
954
	}
955

    
956
	// If there's a PUBLIC entry in this catalog, use it
957
	String resolved = resolveLocalPublic(CatalogEntry.PUBLIC,
958
					     null,
959
					     publicId,
960
					     systemId);
961
	if (resolved != null) {
962
	    return resolved;
963
	}
964

    
965
	// Otherwise, look in the subordinate catalogs
966
	return resolveSubordinateCatalogs(CatalogEntry.PUBLIC,
967
					  null,
968
					  publicId,
969
					  systemId);
970
    }
971

    
972
    /**
973
     * <p>Return the applicable PUBLIC or SYSTEM identifier</p>
974
     *
975
     * <p>This method searches the Catalog and returns the system
976
     * identifier specified for the given system or public identifiers.
977
     * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
978
     * delegated Catalogs are interrogated.</p>
979
     *
980
     * <p>There are four possible cases:</p>
981
     *
982
     * <ul>
983
     * <li>If the system identifier provided matches a SYSTEM entry
984
     * in the current catalog, the SYSTEM entry is returned.
985
     * <li>If the system identifier is not null, the PUBLIC entries
986
     * that were encountered when OVERRIDE YES was in effect are
987
     * interrogated and the first matching entry is returned.</li>
988
     * <li>If the system identifier is null, then all of the PUBLIC
989
     * entries are interrogated and the first matching entry
990
     * is returned. This may not be the same as the preceding case, if
991
     * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
992
     * XML, the only place where a public identifier may occur without
993
     * a system identifier is in a notation declaration.</li>
994
     * <li>Finally, if the public identifier matches one of the partial
995
     * public identifiers specified in a DELEGATE entry in
996
     * the Catalog, the delegated catalog is interrogated. The first
997
     * time that the delegated catalog is required, it will be
998
     * retrieved and parsed. It is subsequently cached.
999
     * </li>
1000
     * </ul>
1001
     *
1002
     * @param entityType The CatalogEntry type for which this query is
1003
     * being conducted. This is necessary in order to do the approprate
1004
     * query on a delegated catalog.
1005
     * @param entityName The name of the entity being searched for, if
1006
     * appropriate.
1007
     * @param publicId The public identifier of the entity in question.
1008
     * @param systemId The nominal system identifier for the entity
1009
     * in question (as provided in the source document).
1010
     *
1011
     * @throws MalformedURLException The formal system identifier of a
1012
     * delegated catalog cannot be turned into a valid URL.
1013
     * @throws IOException Error reading delegated catalog file.
1014
     *
1015
     * @return The system identifier to use.
1016
     * Note that the nominal system identifier is not returned if a
1017
     * match is not found in the catalog, instead null is returned
1018
     * to indicate that no match was found.
1019
     */
1020
    private synchronized String resolveLocalPublic(int entityType,
1021
				      String entityName,
1022
				      String publicId,
1023
				      String systemId)
1024
	throws MalformedURLException, IOException {
1025

    
1026
	// Always normalize the public identifier before attempting a match
1027
	publicId = CatalogReader.normalize(publicId);
1028

    
1029
	// If there's a SYSTEM entry in this catalog, use it
1030
	if (systemId != null) {
1031
	    String resolved = resolveLocalSystem(systemId);
1032
	    if (resolved != null) {
1033
		return resolved;
1034
	    }
1035
	}
1036

    
1037
	// If there's a PUBLIC entry in this catalog, use it
1038
	boolean over = default_override;
1039
	Enumeration enum = catalogEntries.elements();
1040
	while (enum.hasMoreElements()) {
1041
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
1042
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
1043
		over = e.yes_or_no().equalsIgnoreCase("YES");
1044
		continue;
1045
	    }
1046

    
1047
	    if (e.entryType() == CatalogEntry.PUBLIC
1048
		&& e.publicId().equals(publicId)) {
1049
		if (over || systemId == null) {
1050
		    return e.formalSystemIdentifier();
1051
		}
1052
	    }
1053
	}
1054

    
1055
	// If there's a DELEGATE entry in this catalog, use it
1056
	over = default_override;
1057
	enum = catalogEntries.elements();
1058
	Vector delCats = new Vector();
1059
	while (enum.hasMoreElements()) {
1060
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
1061
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
1062
		over = e.yes_or_no().equalsIgnoreCase("YES");
1063
		continue;
1064
	    }
1065

    
1066
	    if (e.entryType() == CatalogEntry.DELEGATE
1067
		&& (over || systemId == null)) {
1068
		String p = (String) e.partialPublicId();
1069
		if (p.length() <= publicId.length()
1070
		    && p.equals(publicId.substring(0, p.length()))) {
1071
		    // delegate this match to the other catalog
1072

    
1073
		    delCats.addElement(e.formalSystemIdentifier());
1074
		}
1075
	    }
1076
	}
1077

    
1078
	if (delCats.size() > 0) {
1079
	    Enumeration enumCats = delCats.elements();
1080

    
1081
	    if (debug > 0) {
1082
		debug(1, "Switching to delegated catalog(s):");
1083
		while (enumCats.hasMoreElements()) {
1084
		    String delegatedCatalog = (String) enumCats.nextElement();
1085
		    debug(1, "\t" + delegatedCatalog);
1086
		}
1087
	    }
1088

    
1089
	    Catalog dcat = new Catalog();
1090
	    dcat.setParserClass(parserClass);
1091
	    dcat.debug = debug;
1092

    
1093
	    enumCats = delCats.elements();
1094
	    while (enumCats.hasMoreElements()) {
1095
		String delegatedCatalog = (String) enumCats.nextElement();
1096
		dcat.parseCatalog(delegatedCatalog);
1097
	    }
1098

    
1099
	    return dcat.resolvePublic(publicId, null);
1100
	}
1101

    
1102
	// Nada!
1103
	return null;
1104
    }
1105

    
1106
    /**
1107
     * <p>Return the applicable SYSTEM system identifier</p>
1108
     *
1109
     * <p>If a SYSTEM entry exists in the Catalog
1110
     * for the system ID specified, return the mapped value.</p>
1111
     *
1112
     * <p>The caller is responsible for doing any necessary
1113
     * normalization of the system identifier before calling
1114
     * this method. For example, a relative system identifier in
1115
     * a document might be converted to an absolute system identifier
1116
     * before attempting to resolve it.</p>
1117
     *
1118
     * <p>On Windows-based operating systems, the comparison between
1119
     * the system identifier provided and the SYSTEM entries in the
1120
     * Catalog is case-insensitive.</p>
1121
     *
1122
     * @param systemId The system ID to locate in the catalog.
1123
     *
1124
     * @return The system identifier to use for the notation.
1125
     *
1126
     * @throws MalformedURLException The formal system identifier of a
1127
     * subordinate catalog cannot be turned into a valid URL.
1128
     * @throws IOException Error reading subordinate catalog file.
1129
     */
1130
    public String resolveSystem(String systemId)
1131
	throws MalformedURLException, IOException {
1132

    
1133
	// If there's a SYSTEM entry in this catalog, use it
1134
	if (systemId != null) {
1135
	    String resolved = resolveLocalSystem(systemId);
1136
	    if (resolved != null) {
1137
		return resolved;
1138
	    }
1139
	}
1140

    
1141
	// Otherwise, look in the subordinate catalogs
1142
	return resolveSubordinateCatalogs(CatalogEntry.SYSTEM,
1143
					  null,
1144
					  null,
1145
					  systemId);
1146
    }
1147

    
1148
    /**
1149
     * <p>Return the applicable SYSTEM system identifier in this
1150
     * catalog.</p>
1151
     *
1152
     * <p>If a SYSTEM entry exists in the catalog file
1153
     * for the system ID specified, return the mapped value.</p>
1154
     *
1155
     * @param systemId The system ID to locate in the catalog
1156
     *
1157
     * @return The mapped system identifier or null
1158
     */
1159
    private String resolveLocalSystem(String systemId) {
1160
	String osname = System.getProperty("os.name");
1161
	boolean windows = (osname.indexOf("Windows") >= 0);
1162
	Enumeration enum = catalogEntries.elements();
1163
	while (enum.hasMoreElements()) {
1164
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
1165
	    if (e.entryType() == CatalogEntry.SYSTEM
1166
		&& (e.systemId().equals(systemId)
1167
		    || (windows
1168
			&& e.systemId().equalsIgnoreCase(systemId)))) {
1169
		return e.formalSystemIdentifier();
1170
	    }
1171
	}
1172
	return null;
1173
    }
1174

    
1175

    
1176
    /**
1177
     * <p>Search the subordinate catalogs, in order, looking for a
1178
     * match.</p>
1179
     *
1180
     * <p>This method searches the Catalog and returns the system
1181
     * identifier specified for the given entity type with the given
1182
     * name, public, and system identifiers. In some contexts, these
1183
     * may be null.</p>
1184
     *
1185
     * @param entityType The CatalogEntry type for which this query is
1186
     * being conducted. This is necessary in order to do the approprate
1187
     * query on a subordinate catalog.
1188
     * @param entityName The name of the entity being searched for, if
1189
     * appropriate.
1190
     * @param publicId The public identifier of the entity in question
1191
     * (as provided in the source document).
1192
     * @param systemId The nominal system identifier for the entity
1193
     * in question (as provided in the source document).
1194
     *
1195
     * @throws MalformedURLException The formal system identifier of a
1196
     * delegated catalog cannot be turned into a valid URL.
1197
     * @throws IOException Error reading delegated catalog file.
1198
     *
1199
     * @return The system identifier to use.
1200
     * Note that the nominal system identifier is not returned if a
1201
     * match is not found in the catalog, instead null is returned
1202
     * to indicate that no match was found.
1203
     */
1204
    private synchronized String resolveSubordinateCatalogs(int entityType,
1205
					      String entityName,
1206
					      String publicId,
1207
					      String systemId)
1208
	throws MalformedURLException, IOException {
1209

    
1210
	for (int catPos = 0; catPos < catalogs.size(); catPos++) {
1211
	    Catalog c = null;
1212

    
1213
	    try {
1214
		c = (Catalog) catalogs.elementAt(catPos);
1215
	    } catch (ClassCastException e) {
1216
		String catfile = (String) catalogs.elementAt(catPos);
1217
		c = new Catalog();
1218
		c.setParserClass(parserClass);
1219
		c.debug = debug;
1220

    
1221
		try {
1222
		    c.parseCatalog(catfile);
1223
		} catch (MalformedURLException mue) {
1224
		    debug(1, "Malformed Catalog URL", catfile);
1225
		} catch (FileNotFoundException fnfe) {
1226
		    debug(1, "Failed to load catalog, file not found",
1227
			  catfile);
1228
		} catch (IOException ioe) {
1229
		    debug(1, "Failed to load catalog, I/O error", catfile);
1230
		}
1231

    
1232
		catalogs.setElementAt(c, catPos);
1233
	    }
1234

    
1235
	    String resolved = null;
1236

    
1237
	    // Ok, now what are we supposed to call here?
1238
	    switch (entityType) {
1239
	    case CatalogEntry.DOCTYPE: {
1240
		resolved = c.resolveDoctype(entityName,
1241
					    publicId,
1242
					    systemId);
1243
		break;
1244
	    }
1245
	    case CatalogEntry.DOCUMENT: {
1246
		resolved = c.resolveDocument();
1247
		break;
1248
	    }
1249
	    case CatalogEntry.ENTITY: {
1250
		resolved = c.resolveEntity(entityName,
1251
					   publicId,
1252
					   systemId);
1253
		break;
1254
	    }
1255
	    case CatalogEntry.NOTATION: {
1256
		resolved = c.resolveNotation(entityName,
1257
					     publicId,
1258
					     systemId);
1259
		break;
1260
	    }
1261
	    case CatalogEntry.PUBLIC: {
1262
		resolved = c.resolvePublic(publicId, systemId);
1263
		break;
1264
	    }
1265
	    case CatalogEntry.SYSTEM: {
1266
		resolved = c.resolveSystem(systemId);
1267
		break;
1268
	    }
1269
	    }
1270

    
1271
	    if (resolved != null) {
1272
		return resolved;
1273
	    }
1274
	}
1275

    
1276
	return null;
1277
    }
1278

    
1279
    // -----------------------------------------------------------------
1280

    
1281
    /**
1282
     * <p>Replace backslashes with forward slashes. (URLs always use
1283
     * forward slashes.)</p>
1284
     *
1285
     * @param sysid The input system identifier.
1286
     * @return The same system identifier with backslashes turned into
1287
     * forward slashes.
1288
     */
1289
    private String fixSlashes (String sysid) {
1290
	return sysid.replace('\\', '/');
1291
    }
1292

    
1293
    /**
1294
     * <p>Construct an absolute URI from a relative one, using the current
1295
     * base URI.</p>
1296
     *
1297
     * @param sysid The (possibly relative) system identifier
1298
     * @return The system identifier made absolute with respect to the
1299
     * current {@link #base}.
1300
     */
1301
    private String makeAbsolute(String sysid) {
1302
	URL local = null;
1303

    
1304
	sysid = fixSlashes(sysid);
1305

    
1306
	try {
1307
	    local = new URL(base, sysid);
1308
	} catch (MalformedURLException e) {
1309
	    debug(1, "Malformed URL on system identifier", sysid);
1310
	}
1311

    
1312
	if (local != null) {
1313
	    return local.toString();
1314
	} else {
1315
	    return sysid;
1316
	}
1317
    }
1318

    
1319
    /**
1320
     * <p>Print debug message (if the debug level is high enough).</p>
1321
     *
1322
     * @param level The debug level of this message. This message
1323
     * will only be
1324
     * displayed if the current debug level is at least equal to this
1325
     * value.
1326
     * @param message The text of the message.
1327
     */
1328
    private void debug(int level, String message) {
1329
	if (debug >= level) {
1330
	    System.out.println(message);
1331
	}
1332
    }
1333

    
1334
    /**
1335
     * <p>Print debug message (if the debug level is high enough).</p>
1336
     *
1337
     * @param level The debug level of this message. This message
1338
     * will only be
1339
     * displayed if the current debug level is at least equal to this
1340
     * value.
1341
     * @param message The text of the message.
1342
     * @param spec An argument to the message.
1343
     */
1344
    private void debug(int level, String message, String spec) {
1345
	if (debug >= level) {
1346
	    System.out.println(message + ": " + spec);
1347
	}
1348
    }
1349

    
1350
    /**
1351
     * <p>Print debug message (if the debug level is high enough).</p>
1352
     *
1353
     * @param level The debug level of this message. This message
1354
     * will only be
1355
     * displayed if the current debug level is at least equal to this
1356
     * value.
1357
     * @param message The text of the message.
1358
     * @param spec1 An argument to the message.
1359
     * @param spec1 Another argument to the message.
1360
     */
1361
    private void debug(int level, String message, String spec1, String spec2) {
1362
	if (debug >= level) {
1363
	    System.out.println(message + ": " + spec1);
1364
	    System.out.println("\t" + spec2);
1365
	}
1366
    }
1367

    
1368
    // -----------------------------------------------------------------
1369

    
1370
    /**
1371
     * <p>Add to the current list of delegated catalogs.</p>
1372
     *
1373
     * <p>This method always constructs the {@link #localDelegate}
1374
     * vector so that it is ordered by length of partial
1375
     * public identifier.</p>
1376
     *
1377
     * @param entry The DELEGATE catalog entry
1378
     */
1379
    private void addDelegate(CatalogEntry entry) {
1380
	int pos = 0;
1381
	String partial = entry.partialPublicId();
1382

    
1383
	Enumeration local = localDelegate.elements();
1384
	while (local.hasMoreElements()) {
1385
	    CatalogEntry dpe = (CatalogEntry) local.nextElement();
1386
	    String dp = dpe.partialPublicId();
1387
	    if (dp.equals(partial)) {
1388
		// we already have this prefix
1389
		return;
1390
	    }
1391
	    if (dp.length() > partial.length()) {
1392
		pos++;
1393
	    }
1394
	    if (dp.length() < partial.length()) {
1395
		break;
1396
	    }
1397
	}
1398

    
1399
	// now insert partial into the vector at [pos]
1400
	if (localDelegate.size() == 0) {
1401
	    localDelegate.addElement(entry);
1402
	} else {
1403
	    localDelegate.insertElementAt(entry, pos);
1404
	}
1405
    }
1406
}
(1-1/11)