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
18package org.apache.commons.logging.impl;
19
20import java.lang.ref.ReferenceQueue;
21import java.lang.ref.WeakReference;
22import 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
115public 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     */
138    public WeakHashtable() {}
139
140
141    /**
142     *@see Hashtable
143     */
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     */
153    public Enumeration elements() {
154        purge();
155        return super.elements();
156    }
157
158    /**
159     *@see Hashtable
160     */
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     */
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     */
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