• 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  * @deprecated Please use {@link java.net.URL#openConnection} instead.
111  *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
112  *     for further details.
113  */
114 @Deprecated
115 public final class WeakHashtable extends Hashtable {
116 
117     /**
118      * The maximum number of times put() or remove() can be called before
119      * the map will be purged of all cleared entries.
120      */
121     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
122 
123     /**
124      * The maximum number of times put() or remove() can be called before
125      * the map will be purged of one cleared entry.
126      */
127     private static final int PARTIAL_PURGE_COUNT     = 10;
128 
129     /* ReferenceQueue we check for gc'd keys */
130     private ReferenceQueue queue = new ReferenceQueue();
131     /* Counter used to control how often we purge gc'd entries */
132     private int changeCount = 0;
133 
134     /**
135      * Constructs a WeakHashtable with the Hashtable default
136      * capacity and load factor.
137      */
WeakHashtable()138     public WeakHashtable() {}
139 
140 
141     /**
142      *@see Hashtable
143      */
containsKey(Object key)144     public boolean containsKey(Object key) {
145         // purge should not be required
146         Referenced referenced = new Referenced(key);
147         return super.containsKey(referenced);
148     }
149 
150     /**
151      *@see Hashtable
152      */
elements()153     public Enumeration elements() {
154         purge();
155         return super.elements();
156     }
157 
158     /**
159      *@see Hashtable
160      */
entrySet()161     public Set entrySet() {
162         purge();
163         Set referencedEntries = super.entrySet();
164         Set unreferencedEntries = new HashSet();
165         for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
166             Map.Entry entry = (Map.Entry) it.next();
167             Referenced referencedKey = (Referenced) entry.getKey();
168             Object key = referencedKey.getValue();
169             Object value = entry.getValue();
170             if (key != null) {
171                 Entry dereferencedEntry = new Entry(key, value);
172                 unreferencedEntries.add(dereferencedEntry);
173             }
174         }
175         return unreferencedEntries;
176     }
177 
178     /**
179      *@see Hashtable
180      */
get(Object key)181     public Object get(Object key) {
182         // for performance reasons, no purge
183         Referenced referenceKey = new Referenced(key);
184         return super.get(referenceKey);
185     }
186 
187     /**
188      *@see Hashtable
189      */
keys()190     public Enumeration keys() {
191         purge();
192         final Enumeration enumer = super.keys();
193         return new Enumeration() {
194             public boolean hasMoreElements() {
195                 return enumer.hasMoreElements();
196             }
197             public Object nextElement() {
198                  Referenced nextReference = (Referenced) enumer.nextElement();
199                  return nextReference.getValue();
200             }
201         };
202     }
203 
204 
205     /**
206      *@see Hashtable
207      */
208     public Set keySet() {
209         purge();
210         Set referencedKeys = super.keySet();
211         Set unreferencedKeys = new HashSet();
212         for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
213             Referenced referenceKey = (Referenced) it.next();
214             Object keyValue = referenceKey.getValue();
215             if (keyValue != null) {
216                 unreferencedKeys.add(keyValue);
217             }
218         }
219         return unreferencedKeys;
220     }
221 
222     /**
223      *@see Hashtable
224      */
225     public Object put(Object key, Object value) {
226         // check for nulls, ensuring symantics match superclass
227         if (key == null) {
228             throw new NullPointerException("Null keys are not allowed");
229         }
230         if (value == null) {
231             throw new NullPointerException("Null values are not allowed");
232         }
233 
234         // for performance reasons, only purge every
235         // MAX_CHANGES_BEFORE_PURGE times
236         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
237             purge();
238             changeCount = 0;
239         }
240         // do a partial purge more often
241         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
242             purgeOne();
243         }
244 
245         Object result = null;
246         Referenced keyRef = new Referenced(key, queue);
247         return super.put(keyRef, value);
248     }
249 
250     /**
251      *@see Hashtable
252      */
253     public void putAll(Map t) {
254         if (t != null) {
255             Set entrySet = t.entrySet();
256             for (Iterator it=entrySet.iterator(); it.hasNext();) {
257                 Map.Entry entry = (Map.Entry) it.next();
258                 put(entry.getKey(), entry.getValue());
259             }
260         }
261     }
262 
263     /**
264      *@see Hashtable
265      */
266     public Collection values() {
267         purge();
268         return super.values();
269     }
270 
271     /**
272      *@see Hashtable
273      */
274     public Object remove(Object key) {
275         // for performance reasons, only purge every
276         // MAX_CHANGES_BEFORE_PURGE times
277         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
278             purge();
279             changeCount = 0;
280         }
281         // do a partial purge more often
282         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
283             purgeOne();
284         }
285         return super.remove(new Referenced(key));
286     }
287 
288     /**
289      *@see Hashtable
290      */
291     public boolean isEmpty() {
292         purge();
293         return super.isEmpty();
294     }
295 
296     /**
297      *@see Hashtable
298      */
299     public int size() {
300         purge();
301         return super.size();
302     }
303 
304     /**
305      *@see Hashtable
306      */
307     public String toString() {
308         purge();
309         return super.toString();
310     }
311 
312     /**
313      * @see Hashtable
314      */
315     protected void rehash() {
316         // purge here to save the effort of rehashing dead entries
317         purge();
318         super.rehash();
319     }
320 
321     /**
322      * Purges all entries whose wrapped keys
323      * have been garbage collected.
324      */
325     private void purge() {
326         synchronized (queue) {
327             WeakKey key;
328             while ((key = (WeakKey) queue.poll()) != null) {
329                 super.remove(key.getReferenced());
330             }
331         }
332     }
333 
334     /**
335      * Purges one entry whose wrapped key
336      * has been garbage collected.
337      */
338     private void purgeOne() {
339 
340         synchronized (queue) {
341             WeakKey key = (WeakKey) queue.poll();
342             if (key != null) {
343                 super.remove(key.getReferenced());
344             }
345         }
346     }
347 
348     /** Entry implementation */
349     private final static class Entry implements Map.Entry {
350 
351         private final Object key;
352         private final Object value;
353 
354         private Entry(Object key, Object value) {
355             this.key = key;
356             this.value = value;
357         }
358 
359         public boolean equals(Object o) {
360             boolean result = false;
361             if (o != null && o instanceof Map.Entry) {
362                 Map.Entry entry = (Map.Entry) o;
363                 result =    (getKey()==null ?
364                                             entry.getKey() == null :
365                                             getKey().equals(entry.getKey()))
366                             &&
367                             (getValue()==null ?
368                                             entry.getValue() == null :
369                                             getValue().equals(entry.getValue()));
370             }
371             return result;
372         }
373 
374         public int hashCode() {
375 
376             return (getKey()==null ? 0 : getKey().hashCode()) ^
377                 (getValue()==null ? 0 : getValue().hashCode());
378         }
379 
380         public Object setValue(Object value) {
381             throw new UnsupportedOperationException("Entry.setValue is not supported.");
382         }
383 
384         public Object getValue() {
385             return value;
386         }
387 
388         public Object getKey() {
389             return key;
390         }
391     }
392 
393 
394     /** Wrapper giving correct symantics for equals and hashcode */
395     private final static class Referenced {
396 
397         private final WeakReference reference;
398         private final int           hashCode;
399 
400         /**
401          *
402          * @throws NullPointerException if referant is <code>null</code>
403          */
404         private Referenced(Object referant) {
405             reference = new WeakReference(referant);
406             // Calc a permanent hashCode so calls to Hashtable.remove()
407             // work if the WeakReference has been cleared
408             hashCode  = referant.hashCode();
409         }
410 
411         /**
412          *
413          * @throws NullPointerException if key is <code>null</code>
414          */
415         private Referenced(Object key, ReferenceQueue queue) {
416             reference = new WeakKey(key, queue, this);
417             // Calc a permanent hashCode so calls to Hashtable.remove()
418             // work if the WeakReference has been cleared
419             hashCode  = key.hashCode();
420 
421         }
422 
423         public int hashCode() {
424             return hashCode;
425         }
426 
427         private Object getValue() {
428             return reference.get();
429         }
430 
431         public boolean equals(Object o) {
432             boolean result = false;
433             if (o instanceof Referenced) {
434                 Referenced otherKey = (Referenced) o;
435                 Object thisKeyValue = getValue();
436                 Object otherKeyValue = otherKey.getValue();
437                 if (thisKeyValue == null) {
438                     result = (otherKeyValue == null);
439 
440                     // Since our hashcode was calculated from the original
441                     // non-null referant, the above check breaks the
442                     // hashcode/equals contract, as two cleared Referenced
443                     // objects could test equal but have different hashcodes.
444                     // We can reduce (not eliminate) the chance of this
445                     // happening by comparing hashcodes.
446                     if (result == true) {
447                         result = (this.hashCode() == otherKey.hashCode());
448                     }
449                     // In any case, as our c'tor does not allow null referants
450                     // and Hashtable does not do equality checks between
451                     // existing keys, normal hashtable operations should never
452                     // result in an equals comparison between null referants
453                 }
454                 else
455                 {
456                     result = thisKeyValue.equals(otherKeyValue);
457                 }
458             }
459             return result;
460         }
461     }
462 
463     /**
464      * WeakReference subclass that holds a hard reference to an
465      * associated <code>value</code> and also makes accessible
466      * the Referenced object holding it.
467      */
468     private final static class WeakKey extends WeakReference {
469 
470         private final Referenced referenced;
471 
472         private WeakKey(Object key,
473                         ReferenceQueue queue,
474                         Referenced referenced) {
475             super(key, queue);
476             this.referenced = referenced;
477         }
478 
479         private Referenced getReferenced() {
480             return referenced;
481         }
482      }
483 }
484