001: /*
002: * Copyright 2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.logging.impl;
018:
019: import java.lang.ref.ReferenceQueue;
020: import java.lang.ref.WeakReference;
021: import java.util.*;
022:
023: /**
024: * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
025: * to hold its keys thus allowing them to be reclaimed by the garbage collector.
026: * The associated values are retained using strong references.</p>
027: *
028: * <p>This class follows the symantics of <code>Hashtable</code> as closely as
029: * possible. It therefore does not accept null values or keys.</p>
030: *
031: * <p><strong>Note:</strong>
032: * This is <em>not</em> intended to be a general purpose hash table replacement.
033: * This implementation is also tuned towards a particular purpose: for use as a replacement
034: * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires
035: * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs
036: * have been made with this in mind.
037: * </p>
038: * <p>
039: * <strong>Usage:</strong> typical use case is as a drop-in replacement
040: * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments
041: * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
042: * allow classloaders to be collected by the garbage collector without the need
043: * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
044: * </p>
045: *
046: * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class
047: * can be supported by the current JVM, and if so then uses it to store
048: * references to the <code>LogFactory</code> implementationd it loads
049: * (rather than using a standard Hashtable instance).
050: * Having this class used instead of <code>Hashtable</code> solves
051: * certain issues related to dynamic reloading of applications in J2EE-style
052: * environments. However this class requires java 1.3 or later (due to its use
053: * of <code>java.lang.ref.WeakReference</code> and associates).
054: * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code>
055: * for backwards compatibility reasons. See the documentation
056: * for method <code>LogFactory.createFactoryStore</code> for more details.</p>
057: *
058: * <p>The reason all this is necessary is due to a issue which
059: * arises during hot deploy in a J2EE-like containers.
060: * Each component running in the container owns one or more classloaders; when
061: * the component loads a LogFactory instance via the component classloader
062: * a reference to it gets stored in the static LogFactory.factories member,
063: * keyed by the component's classloader so different components don't
064: * stomp on each other. When the component is later unloaded, the container
065: * sets the component's classloader to null with the intent that all the
066: * component's classes get garbage-collected. However there's still a
067: * reference to the component's classloader from a key in the "global"
068: * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code>
069: * is called whenever component is unloaded, the classloaders will be correctly
070: * garbage collected; this <i>should</i> be done by any container that
071: * bundles commons-logging by default. However, holding the classloader
072: * references weakly ensures that the classloader will be garbage collected
073: * without the container performing this step. </p>
074: *
075: * <p>
076: * <strong>Limitations:</strong>
077: * There is still one (unusual) scenario in which a component will not
078: * be correctly unloaded without an explicit release. Though weak references
079: * are used for its keys, it is necessary to use strong references for its values.
080: * </p>
081: *
082: * <p> If the abstract class <code>LogFactory</code> is
083: * loaded by the container classloader but a subclass of
084: * <code>LogFactory</code> [LogFactory1] is loaded by the component's
085: * classloader and an instance stored in the static map associated with the
086: * base LogFactory class, then there is a strong reference from the LogFactory
087: * class to the LogFactory1 instance (as normal) and a strong reference from
088: * the LogFactory1 instance to the component classloader via
089: * <code>getClass().getClassLoader()</code>. This chain of references will prevent
090: * collection of the child classloader.</p>
091: *
092: * <p>
093: * Such a situation occurs when the commons-logging.jar is
094: * loaded by a parent classloader (e.g. a server level classloader in a
095: * servlet container) and a custom <code>LogFactory</code> implementation is
096: * loaded by a child classloader (e.g. a web app classloader).</p>
097: *
098: * <p>To avoid this scenario, ensure
099: * that any custom LogFactory subclass is loaded by the same classloader as
100: * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is,
101: * however, rare. The standard LogFactoryImpl class should be sufficient
102: * for most or all users.</p>
103: *
104: *
105: * @author Brian Stansberry
106: *
107: * @since 1.1
108: */
109: public final class WeakHashtable extends Hashtable {
110:
111: /**
112: * The maximum number of times put() or remove() can be called before
113: * the map will be purged of all cleared entries.
114: */
115: private static final int MAX_CHANGES_BEFORE_PURGE = 100;
116:
117: /**
118: * The maximum number of times put() or remove() can be called before
119: * the map will be purged of one cleared entry.
120: */
121: private static final int PARTIAL_PURGE_COUNT = 10;
122:
123: /* ReferenceQueue we check for gc'd keys */
124: private ReferenceQueue queue = new ReferenceQueue();
125: /* Counter used to control how often we purge gc'd entries */
126: private int changeCount = 0;
127:
128: /**
129: * Constructs a WeakHashtable with the Hashtable default
130: * capacity and load factor.
131: */
132: public WeakHashtable() {
133: }
134:
135: /**
136: *@see Hashtable
137: */
138: public boolean containsKey(Object key) {
139: // purge should not be required
140: Referenced referenced = new Referenced(key);
141: return super .containsKey(referenced);
142: }
143:
144: /**
145: *@see Hashtable
146: */
147: public Enumeration elements() {
148: purge();
149: return super .elements();
150: }
151:
152: /**
153: *@see Hashtable
154: */
155: public Set entrySet() {
156: purge();
157: Set referencedEntries = super .entrySet();
158: Set unreferencedEntries = new HashSet();
159: for (Iterator it = referencedEntries.iterator(); it.hasNext();) {
160: Map.Entry entry = (Map.Entry) it.next();
161: Referenced referencedKey = (Referenced) entry.getKey();
162: Object key = referencedKey.getValue();
163: Object value = entry.getValue();
164: if (key != null) {
165: Entry dereferencedEntry = new Entry(key, value);
166: unreferencedEntries.add(dereferencedEntry);
167: }
168: }
169: return unreferencedEntries;
170: }
171:
172: /**
173: *@see Hashtable
174: */
175: public Object get(Object key) {
176: // for performance reasons, no purge
177: Referenced referenceKey = new Referenced(key);
178: return super .get(referenceKey);
179: }
180:
181: /**
182: *@see Hashtable
183: */
184: public Enumeration keys() {
185: purge();
186: final Enumeration enumer = super .keys();
187: return new Enumeration() {
188: public boolean hasMoreElements() {
189: return enumer.hasMoreElements();
190: }
191:
192: public Object nextElement() {
193: Referenced nextReference = (Referenced) enumer
194: .nextElement();
195: return nextReference.getValue();
196: }
197: };
198: }
199:
200: /**
201: *@see Hashtable
202: */
203: public Set keySet() {
204: purge();
205: Set referencedKeys = super .keySet();
206: Set unreferencedKeys = new HashSet();
207: for (Iterator it = referencedKeys.iterator(); it.hasNext();) {
208: Referenced referenceKey = (Referenced) it.next();
209: Object keyValue = referenceKey.getValue();
210: if (keyValue != null) {
211: unreferencedKeys.add(keyValue);
212: }
213: }
214: return unreferencedKeys;
215: }
216:
217: /**
218: *@see Hashtable
219: */
220: public Object put(Object key, Object value) {
221: // check for nulls, ensuring symantics match superclass
222: if (key == null) {
223: throw new NullPointerException("Null keys are not allowed");
224: }
225: if (value == null) {
226: throw new NullPointerException(
227: "Null values are not allowed");
228: }
229:
230: // for performance reasons, only purge every
231: // MAX_CHANGES_BEFORE_PURGE times
232: if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
233: purge();
234: changeCount = 0;
235: }
236: // do a partial purge more often
237: else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
238: purgeOne();
239: }
240:
241: Object result = null;
242: Referenced keyRef = new Referenced(key, queue);
243: return super .put(keyRef, value);
244: }
245:
246: /**
247: *@see Hashtable
248: */
249: public void putAll(Map t) {
250: if (t != null) {
251: Set entrySet = t.entrySet();
252: for (Iterator it = entrySet.iterator(); it.hasNext();) {
253: Map.Entry entry = (Map.Entry) it.next();
254: put(entry.getKey(), entry.getValue());
255: }
256: }
257: }
258:
259: /**
260: *@see Hashtable
261: */
262: public Collection values() {
263: purge();
264: return super .values();
265: }
266:
267: /**
268: *@see Hashtable
269: */
270: public Object remove(Object key) {
271: // for performance reasons, only purge every
272: // MAX_CHANGES_BEFORE_PURGE times
273: if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
274: purge();
275: changeCount = 0;
276: }
277: // do a partial purge more often
278: else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
279: purgeOne();
280: }
281: return super .remove(new Referenced(key));
282: }
283:
284: /**
285: *@see Hashtable
286: */
287: public boolean isEmpty() {
288: purge();
289: return super .isEmpty();
290: }
291:
292: /**
293: *@see Hashtable
294: */
295: public int size() {
296: purge();
297: return super .size();
298: }
299:
300: /**
301: *@see Hashtable
302: */
303: public String toString() {
304: purge();
305: return super .toString();
306: }
307:
308: /**
309: * @see Hashtable
310: */
311: protected void rehash() {
312: // purge here to save the effort of rehashing dead entries
313: purge();
314: super .rehash();
315: }
316:
317: /**
318: * Purges all entries whose wrapped keys
319: * have been garbage collected.
320: */
321: private void purge() {
322: synchronized (queue) {
323: WeakKey key;
324: while ((key = (WeakKey) queue.poll()) != null) {
325: super .remove(key.getReferenced());
326: }
327: }
328: }
329:
330: /**
331: * Purges one entry whose wrapped key
332: * has been garbage collected.
333: */
334: private void purgeOne() {
335:
336: synchronized (queue) {
337: WeakKey key = (WeakKey) queue.poll();
338: if (key != null) {
339: super .remove(key.getReferenced());
340: }
341: }
342: }
343:
344: /** Entry implementation */
345: private final static class Entry implements Map.Entry {
346:
347: private final Object key;
348: private final Object value;
349:
350: private Entry(Object key, Object value) {
351: this .key = key;
352: this .value = value;
353: }
354:
355: public boolean equals(Object o) {
356: boolean result = false;
357: if (o != null && o instanceof Map.Entry) {
358: Map.Entry entry = (Map.Entry) o;
359: result = (getKey() == null ? entry.getKey() == null
360: : getKey().equals(entry.getKey()))
361: && (getValue() == null ? entry.getValue() == null
362: : getValue().equals(entry.getValue()));
363: }
364: return result;
365: }
366:
367: public int hashCode() {
368:
369: return (getKey() == null ? 0 : getKey().hashCode())
370: ^ (getValue() == null ? 0 : getValue().hashCode());
371: }
372:
373: public Object setValue(Object value) {
374: throw new UnsupportedOperationException(
375: "Entry.setValue is not supported.");
376: }
377:
378: public Object getValue() {
379: return value;
380: }
381:
382: public Object getKey() {
383: return key;
384: }
385: }
386:
387: /** Wrapper giving correct symantics for equals and hashcode */
388: private final static class Referenced {
389:
390: private final WeakReference reference;
391: private final int hashCode;
392:
393: /**
394: *
395: * @throws NullPointerException if referant is <code>null</code>
396: */
397: private Referenced(Object referant) {
398: reference = new WeakReference(referant);
399: // Calc a permanent hashCode so calls to Hashtable.remove()
400: // work if the WeakReference has been cleared
401: hashCode = referant.hashCode();
402: }
403:
404: /**
405: *
406: * @throws NullPointerException if key is <code>null</code>
407: */
408: private Referenced(Object key, ReferenceQueue queue) {
409: reference = new WeakKey(key, queue, this );
410: // Calc a permanent hashCode so calls to Hashtable.remove()
411: // work if the WeakReference has been cleared
412: hashCode = key.hashCode();
413:
414: }
415:
416: public int hashCode() {
417: return hashCode;
418: }
419:
420: private Object getValue() {
421: return reference.get();
422: }
423:
424: public boolean equals(Object o) {
425: boolean result = false;
426: if (o instanceof Referenced) {
427: Referenced otherKey = (Referenced) o;
428: Object this KeyValue = getValue();
429: Object otherKeyValue = otherKey.getValue();
430: if (this KeyValue == null) {
431: result = (otherKeyValue == null);
432:
433: // Since our hashcode was calculated from the original
434: // non-null referant, the above check breaks the
435: // hashcode/equals contract, as two cleared Referenced
436: // objects could test equal but have different hashcodes.
437: // We can reduce (not eliminate) the chance of this
438: // happening by comparing hashcodes.
439: if (result == true) {
440: result = (this .hashCode() == otherKey
441: .hashCode());
442: }
443: // In any case, as our c'tor does not allow null referants
444: // and Hashtable does not do equality checks between
445: // existing keys, normal hashtable operations should never
446: // result in an equals comparison between null referants
447: } else {
448: result = this KeyValue.equals(otherKeyValue);
449: }
450: }
451: return result;
452: }
453: }
454:
455: /**
456: * WeakReference subclass that holds a hard reference to an
457: * associated <code>value</code> and also makes accessible
458: * the Referenced object holding it.
459: */
460: private final static class WeakKey extends WeakReference {
461:
462: private final Referenced referenced;
463:
464: private WeakKey(Object key, ReferenceQueue queue,
465: Referenced referenced) {
466: super (key, queue);
467: this .referenced = referenced;
468: }
469:
470: private Referenced getReferenced() {
471: return referenced;
472: }
473: }
474: }
|