• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package org.apache.commons.logging.impl;
19 
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.WeakReference;
22 import java.util.*;
23 
24 /**
25  * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
26  * to hold its keys thus allowing them to be reclaimed by the garbage collector.
27  * The associated values are retained using strong references.</p>
28  *
29  * <p>This class follows the symantics of <code>Hashtable</code> as closely as
30  * possible. It therefore does not accept null values or keys.</p>
31  *
32  * <p><strong>Note:</strong>
33  * This is <em>not</em> intended to be a general purpose hash table replacement.
34  * This implementation is also tuned towards a particular purpose: for use as a replacement
35  * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires
36  * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs
37  * have been made with this in mind.
38  * </p>
39  * <p>
40  * <strong>Usage:</strong> typical use case is as a drop-in replacement
41  * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments
42  * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
43  * allow classloaders to be collected by the garbage collector without the need
44  * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
45  * </p>
46  *
47  * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class
48  * can be supported by the current JVM, and if so then uses it to store
49  * references to the <code>LogFactory</code> implementationd it loads
50  * (rather than using a standard Hashtable instance).
51  * Having this class used instead of <code>Hashtable</code> solves
52  * certain issues related to dynamic reloading of applications in J2EE-style
53  * environments. However this class requires java 1.3 or later (due to its use
54  * of <code>java.lang.ref.WeakReference</code> and associates).
55  * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code>
56  * for backwards compatibility reasons. See the documentation
57  * for method <code>LogFactory.createFactoryStore</code> for more details.</p>
58  *
59  * <p>The reason all this is necessary is due to a issue which
60  * arises during hot deploy in a J2EE-like containers.
61  * Each component running in the container owns one or more classloaders; when
62  * the component loads a LogFactory instance via the component classloader
63  * a reference to it gets stored in the static LogFactory.factories member,
64  * keyed by the component's classloader so different components don't
65  * stomp on each other. When the component is later unloaded, the container
66  * sets the component's classloader to null with the intent that all the
67  * component's classes get garbage-collected. However there's still a
68  * reference to the component's classloader from a key in the "global"
69  * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code>
70  * is called whenever component is unloaded, the classloaders will be correctly
71  * garbage collected; this <i>should</i> be done by any container that
72  * bundles commons-logging by default. However, holding the classloader
73  * references weakly ensures that the classloader will be garbage collected
74  * without the container performing this step. </p>
75  *
76  * <p>
77  * <strong>Limitations:</strong>
78  * There is still one (unusual) scenario in which a component will not
79  * be correctly unloaded without an explicit release. Though weak references
80  * are used for its keys, it is necessary to use strong references for its values.
81  * </p>
82  *
83  * <p> If the abstract class <code>LogFactory</code> is
84  * loaded by the container classloader but a subclass of
85  * <code>LogFactory</code> [LogFactory1] is loaded by the component's
86  * classloader and an instance stored in the static map associated with the
87  * base LogFactory class, then there is a strong reference from the LogFactory
88  * class to the LogFactory1 instance (as normal) and a strong reference from
89  * the LogFactory1 instance to the component classloader via
90  * <code>getClass().getClassLoader()</code>. This chain of references will prevent
91  * collection of the child classloader.</p>
92  *
93  * <p>
94  * Such a situation occurs when the commons-logging.jar is
95  * loaded by a parent classloader (e.g. a server level classloader in a
96  * servlet container) and a custom <code>LogFactory</code> implementation is
97  * loaded by a child classloader (e.g. a web app classloader).</p>
98  *
99  * <p>To avoid this scenario, ensure
100  * that any custom LogFactory subclass is loaded by the same classloader as
101  * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is,
102  * however, rare. The standard LogFactoryImpl class should be sufficient
103  * for most or all users.</p>
104  *
105  *
106  * @author Brian Stansberry
107  *
108  * @since 1.1
109  */
110 public final class WeakHashtable extends Hashtable {
111 
112     /**
113      * The maximum number of times put() or remove() can be called before
114      * the map will be purged of all cleared entries.
115      */
116     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
117 
118     /**
119      * The maximum number of times put() or remove() can be called before
120      * the map will be purged of one cleared entry.
121      */
122     private static final int PARTIAL_PURGE_COUNT     = 10;
123 
124     /* ReferenceQueue we check for gc'd keys */
125     private ReferenceQueue queue = new ReferenceQueue();
126     /* Counter used to control how often we purge gc'd entries */
127     private int changeCount = 0;
128 
129     /**
130      * Constructs a WeakHashtable with the Hashtable default
131      * capacity and load factor.
132      */
WeakHashtable()133     public WeakHashtable() {}
134 
135 
136     /**
137      *@see Hashtable
138      */
containsKey(Object key)139     public boolean containsKey(Object key) {
140         // purge should not be required
141         Referenced referenced = new Referenced(key);
142         return super.containsKey(referenced);
143     }
144 
145     /**
146      *@see Hashtable
147      */
elements()148     public Enumeration elements() {
149         purge();
150         return super.elements();
151     }
152 
153     /**
154      *@see Hashtable
155      */
entrySet()156     public Set entrySet() {
157         purge();
158         Set referencedEntries = super.entrySet();
159         Set unreferencedEntries = new HashSet();
160         for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
161             Map.Entry entry = (Map.Entry) it.next();
162             Referenced referencedKey = (Referenced) entry.getKey();
163             Object key = referencedKey.getValue();
164             Object value = entry.getValue();
165             if (key != null) {
166                 Entry dereferencedEntry = new Entry(key, value);
167                 unreferencedEntries.add(dereferencedEntry);
168             }
169         }
170         return unreferencedEntries;
171     }
172 
173     /**
174      *@see Hashtable
175      */
get(Object key)176     public Object get(Object key) {
177         // for performance reasons, no purge
178         Referenced referenceKey = new Referenced(key);
179         return super.get(referenceKey);
180     }
181 
182     /**
183      *@see Hashtable
184      */
keys()185     public Enumeration keys() {
186         purge();
187         final Enumeration enumer = super.keys();
188         return new Enumeration() {
189             public boolean hasMoreElements() {
190                 return enumer.hasMoreElements();
191             }
192             public Object nextElement() {
193                  Referenced nextReference = (Referenced) enumer.nextElement();
194                  return nextReference.getValue();
195             }
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("Null values are not allowed");
227         }
228 
229         // for performance reasons, only purge every
230         // MAX_CHANGES_BEFORE_PURGE times
231         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
232             purge();
233             changeCount = 0;
234         }
235         // do a partial purge more often
236         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
237             purgeOne();
238         }
239 
240         Object result = null;
241         Referenced keyRef = new Referenced(key, queue);
242         return super.put(keyRef, value);
243     }
244 
245     /**
246      *@see Hashtable
247      */
248     public void putAll(Map t) {
249         if (t != null) {
250             Set entrySet = t.entrySet();
251             for (Iterator it=entrySet.iterator(); it.hasNext();) {
252                 Map.Entry entry = (Map.Entry) it.next();
253                 put(entry.getKey(), entry.getValue());
254             }
255         }
256     }
257 
258     /**
259      *@see Hashtable
260      */
261     public Collection values() {
262         purge();
263         return super.values();
264     }
265 
266     /**
267      *@see Hashtable
268      */
269     public Object remove(Object key) {
270         // for performance reasons, only purge every
271         // MAX_CHANGES_BEFORE_PURGE times
272         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
273             purge();
274             changeCount = 0;
275         }
276         // do a partial purge more often
277         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
278             purgeOne();
279         }
280         return super.remove(new Referenced(key));
281     }
282 
283     /**
284      *@see Hashtable
285      */
286     public boolean isEmpty() {
287         purge();
288         return super.isEmpty();
289     }
290 
291     /**
292      *@see Hashtable
293      */
294     public int size() {
295         purge();
296         return super.size();
297     }
298 
299     /**
300      *@see Hashtable
301      */
302     public String toString() {
303         purge();
304         return super.toString();
305     }
306 
307     /**
308      * @see Hashtable
309      */
310     protected void rehash() {
311         // purge here to save the effort of rehashing dead entries
312         purge();
313         super.rehash();
314     }
315 
316     /**
317      * Purges all entries whose wrapped keys
318      * have been garbage collected.
319      */
320     private void purge() {
321         synchronized (queue) {
322             WeakKey key;
323             while ((key = (WeakKey) queue.poll()) != null) {
324                 super.remove(key.getReferenced());
325             }
326         }
327     }
328 
329     /**
330      * Purges one entry whose wrapped key
331      * has been garbage collected.
332      */
333     private void purgeOne() {
334 
335         synchronized (queue) {
336             WeakKey key = (WeakKey) queue.poll();
337             if (key != null) {
338                 super.remove(key.getReferenced());
339             }
340         }
341     }
342 
343     /** Entry implementation */
344     private final static class Entry implements Map.Entry {
345 
346         private final Object key;
347         private final Object value;
348 
349         private Entry(Object key, Object value) {
350             this.key = key;
351             this.value = value;
352         }
353 
354         public boolean equals(Object o) {
355             boolean result = false;
356             if (o != null && o instanceof Map.Entry) {
357                 Map.Entry entry = (Map.Entry) o;
358                 result =    (getKey()==null ?
359                                             entry.getKey() == null :
360                                             getKey().equals(entry.getKey()))
361                             &&
362                             (getValue()==null ?
363                                             entry.getValue() == null :
364                                             getValue().equals(entry.getValue()));
365             }
366             return result;
367         }
368 
369         public int hashCode() {
370 
371             return (getKey()==null ? 0 : getKey().hashCode()) ^
372                 (getValue()==null ? 0 : getValue().hashCode());
373         }
374 
375         public Object setValue(Object value) {
376             throw new UnsupportedOperationException("Entry.setValue is not supported.");
377         }
378 
379         public Object getValue() {
380             return value;
381         }
382 
383         public Object getKey() {
384             return key;
385         }
386     }
387 
388 
389     /** Wrapper giving correct symantics for equals and hashcode */
390     private final static class Referenced {
391 
392         private final WeakReference reference;
393         private final int           hashCode;
394 
395         /**
396          *
397          * @throws NullPointerException if referant is <code>null</code>
398          */
399         private Referenced(Object referant) {
400             reference = new WeakReference(referant);
401             // Calc a permanent hashCode so calls to Hashtable.remove()
402             // work if the WeakReference has been cleared
403             hashCode  = referant.hashCode();
404         }
405 
406         /**
407          *
408          * @throws NullPointerException if key is <code>null</code>
409          */
410         private Referenced(Object key, ReferenceQueue queue) {
411             reference = new WeakKey(key, queue, this);
412             // Calc a permanent hashCode so calls to Hashtable.remove()
413             // work if the WeakReference has been cleared
414             hashCode  = key.hashCode();
415 
416         }
417 
418         public int hashCode() {
419             return hashCode;
420         }
421 
422         private Object getValue() {
423             return reference.get();
424         }
425 
426         public boolean equals(Object o) {
427             boolean result = false;
428             if (o instanceof Referenced) {
429                 Referenced otherKey = (Referenced) o;
430                 Object thisKeyValue = getValue();
431                 Object otherKeyValue = otherKey.getValue();
432                 if (thisKeyValue == null) {
433                     result = (otherKeyValue == null);
434 
435                     // Since our hashcode was calculated from the original
436                     // non-null referant, the above check breaks the
437                     // hashcode/equals contract, as two cleared Referenced
438                     // objects could test equal but have different hashcodes.
439                     // We can reduce (not eliminate) the chance of this
440                     // happening by comparing hashcodes.
441                     if (result == true) {
442                         result = (this.hashCode() == otherKey.hashCode());
443                     }
444                     // In any case, as our c'tor does not allow null referants
445                     // and Hashtable does not do equality checks between
446                     // existing keys, normal hashtable operations should never
447                     // result in an equals comparison between null referants
448                 }
449                 else
450                 {
451                     result = thisKeyValue.equals(otherKeyValue);
452                 }
453             }
454             return result;
455         }
456     }
457 
458     /**
459      * WeakReference subclass that holds a hard reference to an
460      * associated <code>value</code> and also makes accessible
461      * the Referenced object holding it.
462      */
463     private final static class WeakKey extends WeakReference {
464 
465         private final Referenced referenced;
466 
467         private WeakKey(Object key,
468                         ReferenceQueue queue,
469                         Referenced referenced) {
470             super(key, queue);
471             this.referenced = referenced;
472         }
473 
474         private Referenced getReferenced() {
475             return referenced;
476         }
477      }
478 }
479