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