View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */ 
17  
18  
19  package org.apache.commons.logging.impl;
20  
21  import java.lang.ref.ReferenceQueue;
22  import java.lang.ref.WeakReference;
23  import java.util.*;
24  
25  /**
26   * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
27   * to hold its keys thus allowing them to be reclaimed by the garbage collector.
28   * The associated values are retained using strong references.</p>
29   *
30   * <p>This class follows the symantics of <code>Hashtable</code> as closely as
31   * possible. It therefore does not accept null values or keys.</p>
32   *
33   * <p><strong>Note:</strong>
34   * This is <em>not</em> intended to be a general purpose hash table replacement.
35   * This implementation is also tuned towards a particular purpose: for use as a replacement
36   * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires
37   * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs
38   * have been made with this in mind.
39   * </p>
40   * <p>
41   * <strong>Usage:</strong> typical use case is as a drop-in replacement 
42   * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments
43   * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
44   * allow classloaders to be collected by the garbage collector without the need 
45   * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
46   * </p>
47   *
48   * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class
49   * can be supported by the current JVM, and if so then uses it to store
50   * references to the <code>LogFactory</code> implementationd it loads 
51   * (rather than using a standard Hashtable instance). 
52   * Having this class used instead of <code>Hashtable</code> solves
53   * certain issues related to dynamic reloading of applications in J2EE-style
54   * environments. However this class requires java 1.3 or later (due to its use
55   * of <code>java.lang.ref.WeakReference</code> and associates).
56   * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code>
57   * for backwards compatibility reasons. See the documentation
58   * for method <code>LogFactory.createFactoryStore</code> for more details.</p>
59   *
60   * <p>The reason all this is necessary is due to a issue which
61   * arises during hot deploy in a J2EE-like containers. 
62   * Each component running in the container owns one or more classloaders; when
63   * the component loads a LogFactory instance via the component classloader
64   * a reference to it gets stored in the static LogFactory.factories member,
65   * keyed by the component's classloader so different components don't
66   * stomp on each other. When the component is later unloaded, the container
67   * sets the component's classloader to null with the intent that all the 
68   * component's classes get garbage-collected. However there's still a
69   * reference to the component's classloader from a key in the "global"
70   * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code>
71   * is called whenever component is unloaded, the classloaders will be correctly
72   * garbage collected; this <i>should</i> be done by any container that 
73   * bundles commons-logging by default. However, holding the classloader
74   * references weakly ensures that the classloader will be garbage collected
75   * without the container performing this step. </p>
76   *
77   * <p>
78   * <strong>Limitations:</strong>
79   * There is still one (unusual) scenario in which a component will not 
80   * be correctly unloaded without an explicit release. Though weak references
81   * are used for its keys, it is necessary to use strong references for its values.
82   * </p>
83   * 
84   * <p> If the abstract class <code>LogFactory</code> is 
85   * loaded by the container classloader but a subclass of 
86   * <code>LogFactory</code> [LogFactory1] is loaded by the component's 
87   * classloader and an instance stored in the static map associated with the
88   * base LogFactory class, then there is a strong reference from the LogFactory
89   * class to the LogFactory1 instance (as normal) and a strong reference from
90   * the LogFactory1 instance to the component classloader via
91   * <code>getClass().getClassLoader()</code>. This chain of references will prevent 
92   * collection of the child classloader.</p>
93   *
94   * <p>
95   * Such a situation occurs when the commons-logging.jar is
96   * loaded by a parent classloader (e.g. a server level classloader in a
97   * servlet container) and a custom <code>LogFactory</code> implementation is
98   * loaded by a child classloader (e.g. a web app classloader).</p>
99   * 
100  * <p>To avoid this scenario, ensure
101  * that any custom LogFactory subclass is loaded by the same classloader as 
102  * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is,
103  * however, rare. The standard LogFactoryImpl class should be sufficient
104  * for most or all users.</p>
105  *
106  *
107  * @author Brian Stansberry
108  * 
109  * @since 1.1
110  */
111 public final class WeakHashtable extends Hashtable {
112 
113     /** 
114      * The maximum number of times put() or remove() can be called before
115      * the map will be purged of all cleared entries.
116      */
117     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
118     
119     /** 
120      * The maximum number of times put() or remove() can be called before
121      * the map will be purged of one cleared entry.
122      */
123     private static final int PARTIAL_PURGE_COUNT     = 10;
124     
125     /* ReferenceQueue we check for gc'd keys */
126     private ReferenceQueue queue = new ReferenceQueue();
127     /* Counter used to control how often we purge gc'd entries */
128     private int changeCount = 0;
129     
130     /**
131      * Constructs a WeakHashtable with the Hashtable default
132      * capacity and load factor.
133      */
134     public WeakHashtable() {}
135     
136     
137     /**
138      *@see Hashtable
139      */
140     public boolean containsKey(Object key) {
141         // purge should not be required
142         Referenced referenced = new Referenced(key);
143         return super.containsKey(referenced);
144     }
145     
146     /**
147      *@see Hashtable
148      */
149     public Enumeration elements() {
150         purge();
151         return super.elements();
152     }
153     
154     /**
155      *@see Hashtable
156      */
157     public Set entrySet() {
158         purge();
159         Set referencedEntries = super.entrySet();
160         Set unreferencedEntries = new HashSet();
161         for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
162             Map.Entry entry = (Map.Entry) it.next();
163             Referenced referencedKey = (Referenced) entry.getKey();
164             Object key = referencedKey.getValue();
165             Object value = entry.getValue();
166             if (key != null) {
167                 Entry dereferencedEntry = new Entry(key, value);
168                 unreferencedEntries.add(dereferencedEntry);
169             }
170         }
171         return unreferencedEntries;
172     }
173     
174     /**
175      *@see Hashtable
176      */
177     public Object get(Object key) {
178         // for performance reasons, no purge
179         Referenced referenceKey = new Referenced(key);
180         return super.get(referenceKey);
181     }
182     
183     /**
184      *@see Hashtable
185      */
186     public Enumeration keys() {
187         purge();
188         final Enumeration enumer = super.keys();
189         return new Enumeration() {
190             public boolean hasMoreElements() {
191                 return enumer.hasMoreElements();
192             }
193             public Object nextElement() {
194                  Referenced nextReference = (Referenced) enumer.nextElement();
195                  return nextReference.getValue();
196             }
197         };
198     }
199     
200         
201     /**
202      *@see Hashtable
203      */
204     public Set keySet() {
205         purge();
206         Set referencedKeys = super.keySet();
207         Set unreferencedKeys = new HashSet();
208         for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
209             Referenced referenceKey = (Referenced) it.next();
210             Object keyValue = referenceKey.getValue();
211             if (keyValue != null) {
212                 unreferencedKeys.add(keyValue);
213             }
214         }
215         return unreferencedKeys;
216     }
217     
218     /**
219      *@see Hashtable
220      */    
221     public Object put(Object key, Object value) {
222         // check for nulls, ensuring symantics match superclass
223         if (key == null) {
224             throw new NullPointerException("Null keys are not allowed");
225         }
226         if (value == null) {
227             throw new NullPointerException("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         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 }