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