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    }