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.List;
028import java.util.Map;
029import java.util.Objects;
030import java.util.Set;
031
032/**
033 * Implementation of {@code Hashtable} that uses {@code WeakReference}'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} 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} in {@code LogFactory}. This application requires
044 * good liveliness for {@code get} and {@code put}. 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} used in {@code LogFactory} for J2EE environments
049 * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
050 * allow class loaders 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} 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} implementation it loads
056 * (rather than using a standard Hashtable instance).
057 * Having this class used instead of {@code Hashtable} 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 {@link java.lang.ref.WeakReference} and associates).
061 * And by the way, this extends {@code Hashtable} rather than {@code HashMap}
062 * for backwards compatibility reasons. See the documentation
063 * for method {@code LogFactory.createFactoryStore} 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 class loaders; when
068 * the component loads a LogFactory instance via the component class loader
069 * a reference to it gets stored in the static LogFactory.factories member,
070 * keyed by the component's class loader so different components don't
071 * stomp on each other. When the component is later unloaded, the container
072 * sets the component's class loader 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 class loader from a key in the "global"
075 * {@code LogFactory}'s factories member! If {@code LogFactory.release()}
076 * is called whenever component is unloaded, the class loaders 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 class loader
079 * references weakly ensures that the class loader 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} is
088 * loaded by the container class loader but a subclass of
089 * {@code LogFactory} [LogFactory1] is loaded by the component's
090 * class loader 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 class loader via
094 * {@code getClass().getClassLoader()}. This chain of references will prevent
095 * collection of the child class loader.
096 * <p>
097 * Such a situation occurs when the commons-logging.jar is
098 * loaded by a parent class loader (e.g. a server level class loader in a
099 * servlet container) and a custom {@code LogFactory} implementation is
100 * loaded by a child class loader (e.g. a web app class loader).
101 * <p>
102 * To avoid this scenario, ensure
103 * that any custom LogFactory subclass is loaded by the same class loader as
104 * the base {@code LogFactory}. Creating custom LogFactory subclasses is,
105 * however, rare. The standard LogFactoryImpl class should be sufficient
106 * for most or all users.
107 *
108 * @since 1.1
109 * @deprecated No longer used.
110 */
111@Deprecated
112public final class WeakHashtable extends Hashtable {
113
114    /** Entry implementation */
115    private final static class Entry implements Map.Entry {
116
117        private final Object key;
118        private final Object value;
119
120        private Entry(final Object key, final Object value) {
121            this.key = key;
122            this.value = value;
123        }
124
125        @Override
126        public boolean equals(final Object o) {
127            boolean result = false;
128            if (o instanceof Map.Entry) {
129                final Map.Entry entry = (Map.Entry) o;
130                result =    (getKey()==null ?
131                                            entry.getKey() == null :
132                                            getKey().equals(entry.getKey())) &&
133                            (getValue()==null ?
134                                            entry.getValue() == null :
135                                            getValue().equals(entry.getValue()));
136            }
137            return result;
138        }
139
140        @Override
141        public Object getKey() {
142            return key;
143        }
144
145        @Override
146        public Object getValue() {
147            return value;
148        }
149
150        @Override
151        public int hashCode() {
152            return (getKey()==null ? 0 : getKey().hashCode()) ^
153                (getValue()==null ? 0 : getValue().hashCode());
154        }
155
156        @Override
157        public Object setValue(final Object value) {
158            throw new UnsupportedOperationException("Entry.setValue is not supported.");
159        }
160    }
161
162    /** Wrapper giving correct symantics for equals and hash code */
163    private final static class Referenced {
164
165        private final WeakReference reference;
166        private final int           hashCode;
167
168        /**
169         *
170         * @throws NullPointerException if referant is {@code null}
171         */
172        private Referenced(final Object referant) {
173            reference = new WeakReference(referant);
174            // Calc a permanent hashCode so calls to Hashtable.remove()
175            // work if the WeakReference has been cleared
176            hashCode  = referant.hashCode();
177        }
178
179        /**
180         *
181         * @throws NullPointerException if key is {@code null}
182         */
183        private Referenced(final Object key, final ReferenceQueue queue) {
184            reference = new WeakKey(key, queue, this);
185            // Calc a permanent hashCode so calls to Hashtable.remove()
186            // work if the WeakReference has been cleared
187            hashCode  = key.hashCode();
188
189        }
190
191        @Override
192        public boolean equals(final Object o) {
193            boolean result = false;
194            if (o instanceof Referenced) {
195                final Referenced otherKey = (Referenced) o;
196                final Object thisKeyValue = getValue();
197                final Object otherKeyValue = otherKey.getValue();
198                if (thisKeyValue == null) {
199                    result = otherKeyValue == null;
200
201                    // Since our hash code was calculated from the original
202                    // non-null referant, the above check breaks the
203                    // hash code/equals contract, as two cleared Referenced
204                    // objects could test equal but have different hash codes.
205                    // We can reduce (not eliminate) the chance of this
206                    // happening by comparing hash codes.
207                    result = result && this.hashCode() == otherKey.hashCode();
208                    // In any case, as our c'tor does not allow null referants
209                    // and Hashtable does not do equality checks between
210                    // existing keys, normal hashtable operations should never
211                    // result in an equals comparison between null referants
212                }
213                else
214                {
215                    result = thisKeyValue.equals(otherKeyValue);
216                }
217            }
218            return result;
219        }
220
221        private Object getValue() {
222            return reference.get();
223        }
224
225        @Override
226        public int hashCode() {
227            return hashCode;
228        }
229    }
230
231    /**
232     * WeakReference subclass that holds a hard reference to an
233     * associated {@code value} and also makes accessible
234     * the Referenced object holding it.
235     */
236    private final static class WeakKey extends WeakReference {
237
238        private final Referenced referenced;
239
240        private WeakKey(final Object key,
241                        final ReferenceQueue queue,
242                        final Referenced referenced) {
243            super(key, queue);
244            this.referenced = referenced;
245        }
246
247        private Referenced getReferenced() {
248            return referenced;
249        }
250     }
251
252    /** Serializable version identifier. */
253    private static final long serialVersionUID = -1546036869799732453L;
254
255    /**
256     * The maximum number of times put() or remove() can be called before
257     * the map will be purged of all cleared entries.
258     */
259    private static final int MAX_CHANGES_BEFORE_PURGE = 100;
260
261    /**
262     * The maximum number of times put() or remove() can be called before
263     * the map will be purged of one cleared entry.
264     */
265    private static final int PARTIAL_PURGE_COUNT     = 10;
266
267    /** ReferenceQueue we check for GC'd keys. */
268    private final transient ReferenceQueue queue = new ReferenceQueue();
269
270    /** Counter used to control how often we purge gc'd entries. */
271    private int changeCount;
272
273    /**
274     * Constructs a WeakHashtable with the Hashtable default
275     * capacity and load factor.
276     */
277    public WeakHashtable() {}
278
279    /**
280     *@see Hashtable
281     */
282    @Override
283    public boolean containsKey(final Object key) {
284        // purge should not be required
285        final Referenced referenced = new Referenced(key);
286        return super.containsKey(referenced);
287    }
288
289    /**
290     *@see Hashtable
291     */
292    @Override
293    public Enumeration elements() {
294        purge();
295        return super.elements();
296    }
297
298    /**
299     *@see Hashtable
300     */
301    @Override
302    public Set entrySet() {
303        purge();
304        final Set referencedEntries = super.entrySet();
305        final Set unreferencedEntries = new HashSet();
306        for (final Object referencedEntry : referencedEntries) {
307            final Map.Entry entry = (Map.Entry) referencedEntry;
308            final Referenced referencedKey = (Referenced) entry.getKey();
309            final Object key = referencedKey.getValue();
310            final Object value = entry.getValue();
311            if (key != null) {
312                final Entry dereferencedEntry = new Entry(key, value);
313                unreferencedEntries.add(dereferencedEntry);
314            }
315        }
316        return unreferencedEntries;
317    }
318
319    /**
320     *@see Hashtable
321     */
322    @Override
323    public Object get(final Object key) {
324        // for performance reasons, no purge
325        final Referenced referenceKey = new Referenced(key);
326        return super.get(referenceKey);
327    }
328
329    /**
330     *@see Hashtable
331     */
332    @Override
333    public boolean isEmpty() {
334        purge();
335        return super.isEmpty();
336    }
337
338    /**
339     *@see Hashtable
340     */
341    @Override
342    public Enumeration keys() {
343        purge();
344        final Enumeration enumer = super.keys();
345        return new Enumeration() {
346            @Override
347            public boolean hasMoreElements() {
348                return enumer.hasMoreElements();
349            }
350            @Override
351            public Object nextElement() {
352                 final Referenced nextReference = (Referenced) enumer.nextElement();
353                 return nextReference.getValue();
354            }
355        };
356    }
357
358    /**
359     *@see Hashtable
360     */
361    @Override
362    public Set keySet() {
363        purge();
364        final Set referencedKeys = super.keySet();
365        final Set unreferencedKeys = new HashSet();
366        for (final Object referencedKey : referencedKeys) {
367            final Referenced referenceKey = (Referenced) referencedKey;
368            final Object keyValue = referenceKey.getValue();
369            if (keyValue != null) {
370                unreferencedKeys.add(keyValue);
371            }
372        }
373        return unreferencedKeys;
374    }
375
376    /**
377     * Purges all entries whose wrapped keys
378     * have been garbage collected.
379     */
380    private void purge() {
381        final List toRemove = new ArrayList();
382        synchronized (queue) {
383            WeakKey key;
384            while ((key = (WeakKey) queue.poll()) != null) {
385                toRemove.add(key.getReferenced());
386            }
387        }
388
389        // LOGGING-119: do the actual removal of the keys outside the sync block
390        // to prevent deadlock scenarios as purge() may be called from
391        // non-synchronized methods too
392        final int size = toRemove.size();
393        for (int i = 0; i < size; i++) {
394            super.remove(toRemove.get(i));
395        }
396    }
397
398    /**
399     * Purges one entry whose wrapped key
400     * has been garbage collected.
401     */
402    private void purgeOne() {
403        synchronized (queue) {
404            final WeakKey key = (WeakKey) queue.poll();
405            if (key != null) {
406                super.remove(key.getReferenced());
407            }
408        }
409    }
410
411    /**
412     *@see Hashtable
413     */
414    @Override
415    public synchronized Object put(final Object key, final Object value) {
416        // check for nulls, ensuring semantics match superclass
417        Objects.requireNonNull(key, "key");
418        Objects.requireNonNull(value, "value");
419
420        // for performance reasons, only purge every
421        // MAX_CHANGES_BEFORE_PURGE times
422        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
423            purge();
424            changeCount = 0;
425        }
426        // do a partial purge more often
427        else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
428            purgeOne();
429        }
430
431        final Referenced keyRef = new Referenced(key, queue);
432        return super.put(keyRef, value);
433    }
434
435    /**
436     *@see Hashtable
437     */
438    @Override
439    public void putAll(final Map t) {
440        if (t != null) {
441            final Set entrySet = t.entrySet();
442            for (final Object element : entrySet) {
443                final Map.Entry entry = (Map.Entry) element;
444                put(entry.getKey(), entry.getValue());
445            }
446        }
447    }
448
449    /**
450     * @see Hashtable
451     */
452    @Override
453    protected void rehash() {
454        // purge here to save the effort of rehashing dead entries
455        purge();
456        super.rehash();
457    }
458
459    /**
460     *@see Hashtable
461     */
462    @Override
463    public synchronized Object remove(final Object key) {
464        // for performance reasons, only purge every
465        // MAX_CHANGES_BEFORE_PURGE times
466        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
467            purge();
468            changeCount = 0;
469        }
470        // do a partial purge more often
471        else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
472            purgeOne();
473        }
474        return super.remove(new Referenced(key));
475    }
476
477    /**
478     *@see Hashtable
479     */
480    @Override
481    public int size() {
482        purge();
483        return super.size();
484    }
485
486    /**
487     *@see Hashtable
488     */
489    @Override
490    public String toString() {
491        purge();
492        return super.toString();
493    }
494
495    /**
496     *@see Hashtable
497     */
498    @Override
499    public Collection values() {
500        purge();
501        return super.values();
502    }
503}