001 /*
002 * LAPIS lightweight structured text processing system
003 *
004 * Copyright (C) 1998-2002 Carnegie Mellon University,
005 * Copyright (C) 2003 Massachusetts Institute of Technology.
006 * All rights reserved.
007 *
008 * This library is free software; you can redistribute it
009 * and/or modify it under the terms of the GNU General
010 * Public License as published by the Free Software
011 * Foundation, version 2.
012 *
013 * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014 */
015
016 package lapis;
017
018 import java.util.*;
019 import lapisx.util.*;
020 import lapisx.enum.*;
021 import lapisx.iterator.*;
022
023 /**
024 * A PatternLibrary represents a collection of {@link Parser Parsers} and named {@link Pattern Patterns}.
025 * <p>
026 * A PatternLibrary may have a parent library, from which it inherits parsers and patterns. The collection represented by
027 * a particular library is the union of the parsers and patterns in all of its ancestors, up to the root (global) pattern library.
028 * <p>
029 * At present, LAPIS uses only two levels in this hierarchy. At the root is the global pattern library, a singleton, which
030 * can be obtained with {@link #getGlobalLibrary() getGlobalLibrary}. Its children are the document-specific pattern
031 * libraries, one per Document. Each document-specific library has the global pattern library as its parent.
032 * <p>
033 * The primary purpose of the PatternLibrary is to map between pattern names and Pattern objects.
034 * A client that wants to look up a named pattern (such as Word) normally follows the following
035 * procedure:
036 * <ul>
037 * <li> Fetch the document-specific library using {@link #getDocumentLibrary(Document) getDocumentLibrary}.
038 * <li> Use {@link #lookup(String) lookup} to translate the pattern name (e.g., "Word") into its fully-qualified canonical name (".English.Word")
039 * <li> Use {@link #get(String) get} to map the canonical name into a Pattern object
040 * </ul>
041 * Patterns can be stored in the pattern library with {@link #put(String,Pattern) put()}. Patterns that could apply to any document
042 * should be stored in the global pattern library. Patterns that are meaningful only for one document should be put in the
043 * document-specific library.
044 * <p>
045 * The other responsibility of the PatternLibrary is to manage a set of Parsers. Whenever a parser is added
046 * with {@link #addParser(Parser) addParser()}, the pattern library calls Parser.bind(PatternLibrary) to direct the parser to add its named Patterns to this
047 * library. Whenever a document-specific library is fetched, Parser.bind(PatternLibrary, Document) will be
048 * called on any parsers that haven't seen the document yet, so that parsers with document-specific patterns can add
049 * them to the document-specific library.
050 */
051 public class PatternLibrary implements Cloneable {
052
053 public static Debug debug = Debug.QUIET;
054
055 /**
056 * Global pattern library.
057 */
058 private static PatternLibrary rootLibrary = null;
059
060 /**
061 * String used as key in properties of Document; value is the
062 * doc-specific PatternLibrary
063 */
064 private static String PatternLibraryProperty = "lapis.PatternLibrary";
065
066
067 /**
068 * Dummy pattern returned by get() for an unbound name.
069 */
070 private static final Pattern EMPTY_PATTERN = new Pattern() {
071 public RegionSet match(Document doc) {
072 return RegionSet.EMPTY;
073 }
074 };
075
076 /**
077 * This library's parent, or null if this is the global library.
078 */
079 private PatternLibrary parentLibrary = null;
080
081 /**
082 * Maps final name component (CaselessString) -> canonical name (String) or Vector of canonical names.
083 * Hidden identifiers are not included in this mapping.
084 */
085 private Hashtable names = new Hashtable();
086
087 /**
088 * Maps canonical name (CaselessString starting with ".") -> Pattern
089 */
090 private Hashtable patterns = new Hashtable();
091
092 /**
093 * Collection of parsers.
094 */
095 private Set parsers = new HashSet ();
096
097 /**
098 * Version number. Incremented whenever a new parser is added,
099 * so that getDocumentLibrary() can tell that it needs to ask the parsers
100 * to rebind document-specific patterns.
101 */
102 private long version = 0L;
103
104 /**
105 * Collection of LibraryListeners.
106 */
107 private Set listeners = new SafeIteratorSet ();
108
109
110 /**
111 * Suppress default no-arg constructor. PatternLibrary instances
112 * can only be obtained by getGlobalLibrary() and getDocumentLibrary().
113 */
114 private PatternLibrary() {
115 }
116
117 /**
118 * Get the global pattern library, which is a singleton shared by all documents.
119 * Patterns that should be available to all documents should be stored in this
120 * library. Document-specific patterns should be stored in a document's library
121 * (see getDocumentLibrary). For looking up patterns, it's best
122 * to use getDocumentLibrary(), since a document library automatically falls back
123 * to the global library if a pattern isn't found.
124 */
125 public static PatternLibrary getGlobalLibrary() {
126 if (rootLibrary == null)
127 rootLibrary = new PatternLibrary();
128 return rootLibrary;
129 }
130
131 /**
132 * Get a document's pattern library.
133 * @param doc Document to be searched
134 * @return document-specific pattern library, which includes not only
135 * document-specific patterns but also patterns from the global library.
136 */
137 public static PatternLibrary getDocumentLibrary(Document doc) {
138 PatternLibrary globalLib = getGlobalLibrary();
139
140 if (doc == null)
141 return globalLib;
142
143 PatternLibrary docLib =
144 (PatternLibrary) doc.getProperty(PatternLibraryProperty);
145
146 if (docLib == null) {
147 docLib = new PatternLibrary();
148 docLib.parentLibrary = getGlobalLibrary();
149 doc.putProperty(PatternLibraryProperty, docLib);
150 }
151
152 if (docLib.version != globalLib.version) {
153 // tell all parsers in the global library to bind themselves into the new library
154 docLib.version = globalLib.version;
155 for (Iterator g = globalLib.parsers (); g.hasNext (); ) {
156 Parser parser = (Parser) g.next ();
157 parser.bind (docLib, doc);
158 }
159 }
160
161 return docLib;
162 }
163
164 /**
165 * Get this library's parent library.
166 * @return this library's parent, or null if this is the global library.
167 */
168 public PatternLibrary getParent () {
169 return parentLibrary;
170 }
171
172 /**
173 * Add a parser to this library.
174 * @param parser Parser to add
175 * @effects Adds parser to this library's parser collection, and calls the parser's {@link Parser#bind(PatternLibrary) bind} method.
176 */
177 public synchronized void addParser (Parser parser) {
178 if (parsers.add (parser)) {
179 ++version;
180 parser.bind (this);
181 fireAddedParserEvent (parser);
182 }
183 }
184
185 /**
186 * Remove a parser from this library.
187 * @param parser Parser to remove
188 * @effects Removes parser from this library's parser collection.
189 */
190 public synchronized void removeParser (Parser parser) {
191 parsers.remove (parser);
192 // TODO: tell parser to unbind its patterns
193 }
194
195 /**
196 * Get the parsers in this library and all of its ancestors.
197 * @return iterator that yields the parsers in this library and all of its ancestors.
198 */
199 public Iterator parsers () {
200 Iterator g = parsers.iterator ();
201 if (parentLibrary != null)
202 g = new ConcatIterator (g, parentLibrary.parsers ());
203 return g;
204 }
205
206 /**
207 * Clears all patterns and parsers from this pattern library. Leaves parent
208 * unchanged.
209 */
210 public synchronized void clear() {
211 fireClearEvent();
212 names.clear();
213 patterns.clear();
214 parsers.clear ();
215 }
216
217
218 /**
219 * Get the pattern corresponding to a name.
220 * @param name Canonical name of a pattern, e.g. ".English.Word"
221 * @return Pattern bound to the name in this library. If the name is not bound in this library, recursively
222 * tries to get the named pattern from this library's parent library. If the name is not bound in any of this library's
223 * ancestors, then returns a dummy Pattern object that matches the empty region set on every document.
224 */
225 public synchronized Pattern get(String name) {
226 Object docObj = patterns.get(makePatternsKey(name));
227 if (docObj instanceof Pattern)
228 return (Pattern) docObj;
229 else if (parentLibrary != null)
230 return parentLibrary.get(name);
231 else {
232 if (parentLibrary == null) {
233 return EMPTY_PATTERN;
234 } else {
235 return parentLibrary.get(name);
236 }
237 }
238 }
239
240 /**
241 * Assign a pattern to a name in this library.
242 * @param name Canonical name for pattern, e.g. ".English.Word"
243 * @param pattern Pattern to bind to name
244 * @effects Assigns name to pattern in this library. Doesn't affect this library's parent library.
245 */
246 public synchronized void put(String name, Pattern pattern) {
247 debug.println ("binding " + name + " to " + pattern.getClass ());
248 CaselessString rskey = makePatternsKey(name);
249
250 if (patterns.put(rskey, pattern) != null)
251 return; // name already existed
252
253 // Add base name -> canonical name mapping to names hash
254 if (!isHidden(name)) {
255 CaselessString nkey = makeNamesKey(name);
256 if (name.startsWith("."))
257 name = name.substring(1);
258 Object obj = names.get(nkey);
259 if (obj == null)
260 names.put(nkey, name);
261 else {
262 Vector v;
263 if (obj instanceof String) {
264 v = new Vector();
265 v.addElement(obj);
266 names.put(nkey, v);
267 } else
268 v = (Vector) obj;
269
270 v.addElement(name);
271 }
272 }
273
274 //this preserves invariant of NO duplicates
275 // if (parentLibrary != null)
276 // this.parentLibrary.recursiveRemove(name);
277
278 fireAddedEvent(name);
279 }
280
281 /**
282 * Rename a pattern.
283 * @param oldName old canonical name for pattern
284 * @param newName new canonical name
285 * @effects Locates pattern named oldName by searching for it, first in this library, and failing that, in this library's
286 * ancestors. In <b>every</b> library where oldName is found, the assignment to oldName is removed, and the pattern is
287 * rebound to newName instead.
288 * @return true if oldName was found in this library or one of its ancestors; false if not.
289 */
290 public synchronized boolean rename(String oldName, String newName) {
291 boolean found = patterns.containsKey(makePatternsKey(oldName));
292 if (found) {
293 Pattern pat = get(oldName);
294 remove(oldName);
295 put(newName, pat);
296 }
297
298 if (parentLibrary != null)
299 found |= parentLibrary.rename(oldName, newName);
300
301 return found;
302 }
303
304 /**
305 * Remove named pattern from this library and all of its ancestors.
306 * @param name Canonical name of pattern to remove
307 * @effects Removes pattern from this library and all its ancestors, if found.
308 */
309 public synchronized void remove (String name) {
310 // First delete name -> pattern mapping
311 if (patterns.remove (makePatternsKey(name)) != null) {
312
313 // Then delete basename -> name mapping from names hash
314 if (!isHidden(name)) {
315 CaselessString key = makeNamesKey(name);
316 Object obj = names.get(key);
317 if (obj == null); // name name not found
318 else {
319 if (obj instanceof String)
320 names.remove(key);
321 else {
322 Vector v = (Vector) obj;
323 v.removeElement(name);
324 if (v.isEmpty())
325 names.remove(key);
326 }
327 }
328 }
329
330 fireRemovedEvent(name);
331 }
332
333 // finally, delete from ancestors
334 if (parentLibrary != null)
335 parentLibrary.remove (name);
336 }
337
338 /**
339 * Test whether this library or one of its ancestors contains a named pattern.
340 * @param name Canonical name of pattern to test for
341 * @return true if name is found in this library or one of its ancestors; false if not.
342 */
343 public synchronized boolean contains(String name) {
344 return patterns.containsKey(makePatternsKey(name))
345 || (parentLibrary != null && parentLibrary.contains(name));
346 //getDefaultLibrary().patterns.containsKey (makePatternsKey (name));
347 }
348
349 /**
350 * Convert a pattern name into a canonical name, relative to the top-level namespace. See {@link #lookup(String,String) lookup(String,String)} for
351 * more details.
352 * @param name Relative or canonical name for pattern, e.g. "Word".
353 * @return canonical name for the pattern (e.g. ".English.Word"), assuming only one named pattern matches in this
354 * library or any of its ancestors.
355 * @throws LabelNotFoundException if no names in the pattern library or its ancestors match the short pattern name.
356 * @throws LabelAmbiguousException if more than one name in the pattern library or its ancestors matches the short name.
357 */
358 public synchronized String lookup(String name)
359 throws LabelNotFoundException, LabelAmbiguousException {
360 return lookup(name, "");
361 }
362
363 /**
364 * Convert a short pattern name into a canonical name, given a preferred namespace.
365 * <p>
366 * A canonical pattern name begins with a period (e.g. ".English.Word.AllCapsWord").
367 * A relative pattern name is any proper suffix (on period boundaries) of a canonical name, e.g. "AllCapsWord",
368 * "Word.AllCapsWord", and "English.Word.AllCapsWord".
369 * <p>
370 * This method resolves a name relative to the preferred namespace, using the following rules:
371 * <ol>
372 * <li>If the name is already canonical, and it appears in this library or its ancestors, it is returned unchanged.
373 * <li>If namespace+"."+name is found in this library or its ancestors, then namespace+"."+name is returned.
374 * <li>Otherwise, all canonical names in this library or its ancestors that have name as a proper suffix are returned.
375 * </ol>
376 * If exactly one canonical name matches step #3, then it is returned. If fewer than one or more than one canonical names
377 * match, then an exception is thrown.
378 * @param name Relative or canonical name for pattern, e.g. "Word".
379 * @param namespace Preferred namespace for relative name lookups. Must be canonical, i.e. starting with ".".
380 * May be null if no namespace should be preferred (in which case step #2 is skipped above).
381 * @return canonical name for the pattern (e.g. ".English.Word"), assuming only one named pattern matches in this
382 * library or any of its ancestors.
383 * @throws LabelNotFoundException if no names in the pattern library or its ancestors match the short pattern name.
384 * @throws LabelAmbiguousException if more than one name in the pattern library or its ancestors matches the short name.
385 */
386 public synchronized String lookup(String name, String namespace)
387 throws LabelNotFoundException, LabelAmbiguousException {
388
389 // Names starting with . are already canonical -- strip the period
390 // and look it up in the patterns hash
391 if (name.startsWith(".")) {
392 String canonical = name.substring(1);
393 // if (patterns.containsKey (makePatternsKey (canonical)))
394 if (this.contains(canonical))
395 return canonical;
396 else {
397 // throw new LabelNotFoundException (name);
398
399 if (parentLibrary == null)
400 throw new LabelNotFoundException(name);
401 else
402 return parentLibrary.lookup(name, namespace);
403 }
404
405 }
406
407 // First look for name in specified namespace.
408 String specific =
409 (namespace != null && namespace.length() > 0)
410 ? namespace + "." + name
411 : name;
412 // if (patterns.containsKey (makePatternsKey (specific)))
413 if (this.contains(specific))
414 return specific;
415
416 // Then search for the name in all namespaces.
417 // If it appears exactly once, return that one; otherwise
418 // throw LabelAmbiguous or LabelNotFound.
419
420 // Look it up in names mapping to get 0 or more canonical names
421 // ending with that base
422
423 Object obj = names.get(makeNamesKey(name));
424
425 /**
426
427 TOTAL HACK!
428
429 Object docSpecObj = names.get (makeNamesKey (name));
430 Object genObj = getDefaultLibrary().names.get(makeNamesKey (name));
431 if (this == getDefaultLibrary()) genObj = null;
432
433 Object obj = null;
434 if (docSpecObj instanceof String && genObj == null) {
435 obj = docSpecObj;
436 }
437 if (genObj instanceof String && docSpecObj == null) {
438 obj = genObj;
439 }
440 if (docSpecObj instanceof Vector &&
441 genObj instanceof Vector) {
442 obj = new Vector((Vector)docSpecObj);
443 ((Vector)obj).addAll((Vector)genObj);
444 }
445 if (docSpecObj instanceof Vector && !(genObj instanceof Vector)) {
446 obj = new Vector((Vector)docSpecObj);
447 if (genObj instanceof String) {
448 ((Vector)obj).add(genObj);
449 }
450 }
451 if (genObj instanceof Vector && !(docSpecObj instanceof Vector)) {
452 obj = new Vector((Vector)genObj);
453 if (docSpecObj instanceof String) {
454 ((Vector)obj).add(docSpecObj);
455 }
456 }
457 */
458
459 if (obj instanceof String) {
460 String canonical = (String) obj;
461
462 if (keyEndsWith(canonical, name))
463 return canonical;
464 else {
465 //throw new LabelNotFoundException (name);
466 if (parentLibrary == null)
467 throw new LabelNotFoundException(name);
468 else
469 return parentLibrary.lookup(name, namespace);
470 }
471 } else if (obj instanceof Vector) {
472 Vector v = (Vector) obj;
473 String match = null;
474 Vector multipleMatches = null;
475
476 for (Enumeration e = v.elements(); e.hasMoreElements();) {
477 String canonical = (String) e.nextElement();
478 if (keyEndsWith(canonical, name)) {
479 if (multipleMatches != null || match != null) {
480 if (multipleMatches == null) {
481 multipleMatches = new Vector();
482 multipleMatches.addElement(match);
483 }
484 multipleMatches.addElement(canonical);
485 } else if (match == null)
486 match = canonical;
487 }
488 }
489
490 if (match == null) {
491 // throw new LabelNotFoundException (name);
492 if (parentLibrary == null)
493 throw new LabelNotFoundException(name);
494 else
495 return parentLibrary.lookup(name, namespace);
496 } else if (multipleMatches != null)
497 throw new LabelAmbiguousException(name, multipleMatches);
498 else
499 return match;
500 } else {
501 //throw new LabelNotFoundException (name);
502 if (parentLibrary == null)
503 throw new LabelNotFoundException(name);
504 else
505 return parentLibrary.lookup(name, namespace);
506 }
507 }
508
509 /**
510 * Get the visible names in the library (i.e., names that don't
511 * begin with an underscore).
512 * @return Enumeration of Strings, the canonical names of the
513 * visible identifiers in this library and its ancestors
514 */
515 public Enumeration visibleNames() {
516 Enumeration e = null;
517 if (parentLibrary == null)
518 e = patterns.keys();
519 else
520 e =
521 new ConcatEnumeration(
522 patterns.keys(),
523 parentLibrary.visibleNames());
524
525 return new FilteredEnumeration(e) {
526 public void transform(Object o) {
527 String name = o.toString();
528 if (!isHidden(name))
529 yield(name);
530 }
531 };
532 }
533
534 /**
535 * Get all names in this library and its ancestors (both visible and hidden).
536 * @return Enumeration of Strings, the canonical names of the
537 * identifiers in this library and its ancestors
538 */
539 public Enumeration allNames() {
540 Enumeration e = null;
541 if (parentLibrary == null)
542 e = patterns.keys();
543 else
544 e =
545 new ConcatEnumeration(
546 patterns.keys(),
547 parentLibrary.visibleNames());
548
549 return new FilteredEnumeration(e) {
550 public void transform(Object o) {
551 yield(o.toString());
552 }
553 };
554 }
555
556 /**
557 * Get number of named patterns in this library and its ancestors.
558 * @return number of names that would be enumerated by {@link #allNames}.
559 */
560 public int getSize() {
561 if (parentLibrary == null)
562 return patterns.size();
563 else
564 return patterns.size() + this.parentLibrary.getSize();
565 }
566
567 /**
568 * Get shortest unambiguous name for a canonical name.
569 * @param canonical canonical pattern name, e.g. ".English.Word.AllCapsWord"
570 * @return shortest unambiguous relative name for this pattern, e.g. "AllCapsWord"
571 */
572 public synchronized String getShortestName(String canonical) {
573 if (canonical.startsWith("."))
574 canonical = canonical.substring(1);
575
576 for (int i = canonical.lastIndexOf('.');
577 i != -1;
578 i = canonical.lastIndexOf('.', i - 1)) {
579 try {
580 String abbr = canonical.substring(i + 1);
581 if (lookup(abbr).equalsIgnoreCase(canonical))
582 return abbr;
583 } catch (LookupException e) {
584 //added - MN
585 if (parentLibrary != null)
586 parentLibrary.getShortestName(canonical);
587 }
588 }
589
590 return canonical;
591 }
592
593 /**
594 * Extract the last component of a name, i.e.,
595 * the string following the last period.
596 * @param name Pattern name (either canonical or relative), e.g. "English.Word"
597 * @return component of name following last period, e.g. "Word"
598 */
599 public static String getBasename(String name) {
600 if (name.startsWith("."))
601 name = name.substring(1);
602
603 int lastPeriod = name.lastIndexOf('.');
604 return (lastPeriod == -1) ? name : name.substring(lastPeriod + 1);
605 }
606
607 /**
608 * Test whether name is a hidden identifier.
609 * @param name Identifier to test
610 * @return true iff name's last component (its base name)
611 * starts with an underscore
612 */
613 public static boolean isHidden(String name) {
614 return getBasename(name).startsWith("_");
615 }
616
617 static boolean keyEndsWith(String key, String suffix) {
618 int keyLen = key.length();
619 int suffixLen = suffix.length();
620 int pos = keyLen - suffixLen;
621 return pos >= 0
622 && key.substring(pos).equalsIgnoreCase(suffix)
623 && (pos == 0 || key.charAt(pos - 1) == '.');
624 }
625
626 static CaselessString makePatternsKey(String name) {
627 if (name.startsWith("."))
628 name = name.substring(1);
629 return new CaselessString(name);
630 }
631
632 static CaselessString makeNamesKey(String name) {
633 return new CaselessString(getBasename(name));
634 }
635
636 /**
637 * Base class for exceptions thrown by lookup().
638 */
639 public static class LookupException extends Exception {
640 String name;
641 public LookupException(String name, String message) {
642 super(message);
643 this.name = name;
644 }
645 public String getName() {
646 return name;
647 }
648 }
649
650 /**
651 * Exception thrown when lookup() finds no matching pattern names.
652 */
653 public static class LabelNotFoundException extends LookupException {
654 public LabelNotFoundException(String name) {
655 super(name, "there is no pattern named " + name.toUpperCase());
656 }
657 }
658
659 /**
660 * Exception thrown when lookup() finds multiple matching pattern names.
661 */
662 public static class LabelAmbiguousException extends LookupException {
663 Vector choices;
664 public LabelAmbiguousException(String name, Vector choices) {
665 super(name, "pattern name " + name.toUpperCase() + " is ambiguous");
666 this.choices = choices;
667 }
668 public String[] getChoices() {
669 String[] result = new String[choices.size()];
670 choices.copyInto(result);
671 return result;
672 }
673 }
674
675 //
676 // Event listeners
677 //
678
679 /**
680 * Listener notified when patterns are added and removed from library.
681 */
682 public static interface LibraryListener {
683 /**
684 * Called when a pattern is bound into the pattern library using put().
685 * @param lib PatternLibrary that changed
686 * @param name name that was bound
687 */
688 public void patternAdded(PatternLibrary lib, String name);
689 /**
690 * Called when a pattern name is removed from the pattern library by remove() or clear().
691 * @param lib PatternLibrary that changed
692 * @param name name that was removed
693 */
694 public void patternRemoved(PatternLibrary lib, String name);
695 /**
696 * Called when a parser is added to the pattern library using addParser().
697 * @param lib PatternLibrary that changed
698 * @param parser Parser that was added
699 */
700 public void parserAdded (PatternLibrary lib, Parser parser);
701 }
702
703 /**
704 * Add a listener to this library. The listener receives events that affect this library and all its ancestors.
705 * @param listener Listener to receive library events
706 * @effects Registers listener for future events from this library and its ancestors.
707 */
708 public synchronized void addLibraryListener(LibraryListener listener) {
709 debug.println("adding library listener " + listener.getClass());
710 listeners.add (listener);
711 if (parentLibrary != null)
712 parentLibrary.addLibraryListener (listener);
713 }
714
715 /**
716 * Removes a listener from this library and all its ancestors.
717 * @param listener Listener to stop receiving library events
718 * @effects Listener no longer receives events from this library or any of its ancestors.
719 */
720 public synchronized void removeLibraryListener(LibraryListener listener) {
721 listeners.remove (listener);
722 if (parentLibrary != null)
723 parentLibrary.removeLibraryListener (listener);
724 }
725
726 private synchronized void fireAddedEvent(String name) {
727 debug.println("**** firing added event: " + name);
728 if (name.startsWith("."))
729 name = name.substring(1);
730
731 for (Iterator g = listeners.iterator (); g.hasNext (); )
732 ((LibraryListener) g.next ()).patternAdded (this, name);
733 }
734
735 private synchronized void fireRemovedEvent(String name) {
736 debug.println("PATTERN REMOVE:" + name);
737 if (name.startsWith("."))
738 name = name.substring(1);
739
740 for (Iterator g = listeners.iterator (); g.hasNext (); )
741 ((LibraryListener) g.next ()).patternRemoved (this, name);
742 }
743
744 private synchronized void fireAddedParserEvent (Parser parser) {
745 for (Iterator g = listeners.iterator (); g.hasNext (); )
746 ((LibraryListener) g.next ()).parserAdded (this, parser);
747 }
748
749 private synchronized void fireClearEvent() {
750 debug.println("*** in fire clear event...");
751 Iterator names = patterns.keySet().iterator();
752 while (names.hasNext()) {
753 CaselessString temp = (CaselessString) names.next();
754 debug.println("*** removing: " + temp);
755 fireRemovedEvent(temp.toString());
756 }
757 }
758 }