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    
018    package org.apache.commons.logging.impl;
019    
020    import java.lang.ref.ReferenceQueue;
021    import java.lang.ref.WeakReference;
022    import java.util.ArrayList;
023    import java.util.Collection;
024    import java.util.Enumeration;
025    import java.util.HashSet;
026    import java.util.Hashtable;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import 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 862526 2013-05-20 17:07:42Z tn $
109     * @since 1.1
110     */
111    public 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    }