001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext.html.dtd;
015:
016: import java.io.Reader;
017: import java.lang.ref.WeakReference;
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.LinkedList;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026:
027: /**
028: * This class stores references to DTDReaderProviders. It also acts as a cache
029: * for parsed DTDs and as the only factory for creating DTDs.
030: *
031: * @author Petr Nejedly
032: * @version 1.0
033: */
034: public class Registry {
035:
036: /**
037: * The map [DTD public identifier -> Weak reference(DTD)] Works as a DTD
038: * cache.
039: */
040: private static Map dtdMap = new HashMap();
041:
042: /** List of all registered providers we use for parsing DTDs with */
043: private static List providers = new ArrayList();
044:
045: /** The list of all listeners for DTD invalidate events. */
046: private static LinkedList listeners = new LinkedList();
047:
048: /**
049: * The map [DTDReaderProvider -> Set[String]] representing the public
050: * identifiers of the DTDs parsed by this provider] Used when the provider
051: * is invalidated/removed.
052: */
053: private static Map provider2dtds = new HashMap();
054:
055: /**
056: * Add a DTD.InvalidateListener to the listener list. The listener will be
057: * notified when any DTD is changed. The listeners are referenced weakly, so
058: * they are removed automatically when they are no longer in use.
059: *
060: * @param listener
061: * The DTD.InvalidateListener to be added.
062: */
063: public void addInvalidateListener(InvalidateListener listener) {
064: synchronized (listeners) {
065: listeners.add(new WeakReference(listener));
066: }
067: }
068:
069: /**
070: * Remove a DTD.InvalidateListener from teh listener list. The listeners are
071: * referenced weakly, so they are removed automatically when they are no
072: * longer in use.
073: *
074: * @param listener
075: * The DTD.InvalidateListener to be removed.
076: */
077: public void removeInvalidateListener(InvalidateListener listener) {
078: synchronized (listeners) {
079: // Iterator on LinkedList allows remove()
080: for (Iterator it = listeners.iterator(); it.hasNext();) {
081: WeakReference ref = (WeakReference) it.next();
082: InvalidateListener obj = (InvalidateListener) ref.get();
083: // remove required or gc()ed references
084: if (obj == null || obj == listener)
085: it.remove();
086: }
087: }
088: }
089:
090: /**
091: * Report an invalidation event to all registered listeners.
092: *
093: * @param identifiers
094: * The set of Strings representing the public identifiers of
095: * invalidated DTDs.
096: */
097: public static void fireInvalidateEvent(Set identifiers) {
098: // 1. clean up our cache
099: for (Iterator it = identifiers.iterator(); it.hasNext();) {
100: dtdMap.remove(it.next());
101: }
102:
103: // 2. gather all valid listeners, throw away those already dead.
104: java.util.List targets = new ArrayList();
105: synchronized (listeners) {
106: for (Iterator it = listeners.iterator(); it.hasNext();) {
107: WeakReference ref = (WeakReference) it.next();
108: InvalidateListener obj = (InvalidateListener) ref.get();
109: if (obj == null) {
110: it.remove();
111: } else {
112: targets.add(obj);
113: }
114: }
115: }
116:
117: // 3. distribute the event
118: InvalidateEvent evt = new InvalidateEvent(identifiers);
119: for (Iterator it = targets.iterator(); it.hasNext();) {
120: InvalidateListener l = (InvalidateListener) it.next();
121: l.dtdInvalidated(evt);
122: }
123: }
124:
125: /**
126: * Add DTDReaderProvider. It can be then used for parsing the DTD.
127: *
128: * @param provider
129: * The ReaderProvider capable of providing any number of streams
130: * for any public identifier. It shall be able to provide streams
131: * for all public identifiers referenced from its streams.
132: */
133: public static void registerReaderProvider(ReaderProvider provider) {
134: providers.add(provider);
135: }
136:
137: /**
138: * Destroy all DTDs parsed from Readers provided by this provider and notify
139: * all registered users of such DTDs they are invalid now
140: */
141: public static void invalidateReaderProvider(ReaderProvider provider) {
142: Set identifiers = (Set) provider2dtds.get(provider);
143:
144: if (identifiers != null) {
145: // 2. clean up our cache and notify all registered users
146: // of affected DTDs.
147: fireInvalidateEvent(identifiers);
148:
149: // 3. free the provider from the parsed refistry
150: provider2dtds.remove(provider);
151: }
152: }
153:
154: /**
155: * Remove given ReaderProvider from usage, destroy all DTDs parsed from
156: * Readers provided by this provider and notify all registered users of such
157: * DTDs they are invalid now
158: */
159: public static void unregisterReaderProvider(ReaderProvider provider) {
160: // remove it from our provider list to not use it any more
161: providers.remove(provider);
162:
163: invalidateReaderProvider(provider);
164: }
165:
166: /**
167: * The "smart" method for accessing the items in the table, cares of the
168: * weak indirection and cleaning up the gc()ed cells.
169: */
170: private static DTD getWeak(String identifier) {
171: WeakReference ref = (WeakReference) dtdMap.get(identifier);
172: if (ref == null) // don't know even the key
173: return null;
174:
175: DTD dtd = (DTD) ref.get();
176: if (dtd == null) // gc()ed in the mean time, clean up the table
177: dtdMap.remove(identifier);
178:
179: return dtd;
180: }
181:
182: /** The method for storing an item into the map as a weak reference */
183: private static void putWeak(String identifier, DTD dtd) {
184: dtdMap.put(identifier, new WeakReference(dtd));
185: }
186:
187: private static ReaderProvider getProvider(String identifier,
188: String fileName) {
189: for (Iterator i = providers.iterator(); i.hasNext();) {
190: ReaderProvider prov = (ReaderProvider) i.next();
191: Reader reader = prov.getReaderForIdentifier(identifier,
192: fileName);
193: if (reader != null)
194: return prov;
195: }
196: return null;
197: }
198:
199: private static DTD parseDTD(String identifier, String fileName) {
200: ReaderProvider prov = getProvider(identifier, fileName);
201: if (prov == null)
202: return null;
203: try {
204: DTD dtd = new DTDParser().createDTD(prov, identifier,
205: fileName);
206: if (dtd != null) {
207: Set dtdSet = (Set) provider2dtds.get(prov);
208: if (dtdSet == null) {
209: dtdSet = new HashSet();
210: provider2dtds.put(prov, dtdSet);
211: }
212:
213: dtdSet.add(identifier);
214: putWeak(identifier, dtd);
215: }
216: return dtd;
217: } catch (DTDParser.WrongDTDException exc) {
218: // System.err.println("TODO (DTD.java:90): Notify the error during
219: // parsing." );
220: return null;
221: }
222: }
223:
224: /**
225: * Get the DTD identified by its public identifier, exact match of
226: * identifier is required.
227: *
228: * @param identifier
229: * public identifier of required DTD, e.g. "-//W3C//DTD HTML
230: * 4.01//EN"
231: * @param fileName
232: * the name of file for this DTD, is used only as a helper for
233: * lookup, could be <CODE>null</CODE>, or could be e.g. URL to
234: * the DTD on the internet, in which case proper
235: * DTDReaderProvider could try to fetch it from there.
236: * @return implementation of DTD interface for given public identifier, or
237: * null, if no such DTD is cached and could not be created from
238: * registered DTDReaderProviders.
239: */
240: public static DTD getDTD(String identifier, String fileName) {
241: DTD dtd = getWeak(identifier);
242:
243: if (dtd == null)
244: dtd = parseDTD(identifier, fileName);
245:
246: return dtd;
247: }
248: }
|