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