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 * 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 ?
140                                            entry.getKey() == null :
141                                            getKey().equals(entry.getKey())) &&
142                            (getValue()==null ?
143                                            entry.getValue() == null :
144                                            getValue().equals(entry.getValue()));
145            }
146            return result;
147        }
148
149        @Override
150        public Object getKey() {
151            return key;
152        }
153
154        @Override
155        public Object getValue() {
156            return value;
157        }
158
159        @Override
160        public int hashCode() {
161            return (getKey()==null ? 0 : getKey().hashCode()) ^
162                (getValue()==null ? 0 : getValue().hashCode());
163        }
164
165        @Override
166        public Object setValue(final Object value) {
167            throw new UnsupportedOperationException("Entry.setValue is not supported.");
168        }
169    }
170
171    /** Wrapper giving correct symantics for equals and hash code */
172    private final static class Referenced {
173
174        private final WeakReference reference;
175        private final int           hashCode;
176
177        /**
178         *
179         * @throws NullPointerException if referant is {@code null}
180         */
181        private Referenced(final Object referant) {
182            reference = new WeakReference(referant);
183            // Calc a permanent hashCode so calls to Hashtable.remove()
184            // work if the WeakReference has been cleared
185            hashCode  = referant.hashCode();
186        }
187
188        /**
189         *
190         * @throws NullPointerException if key is {@code null}
191         */
192        private Referenced(final Object key, final ReferenceQueue queue) {
193            reference = new WeakKey(key, queue, this);
194            // Calc a permanent hashCode so calls to Hashtable.remove()
195            // work if the WeakReference has been cleared
196            hashCode  = key.hashCode();
197
198        }
199
200        @Override
201        public boolean equals(final Object o) {
202            boolean result = false;
203            if (o instanceof Referenced) {
204                final Referenced otherKey = (Referenced) o;
205                final Object thisKeyValue = getValue();
206                final Object otherKeyValue = otherKey.getValue();
207                if (thisKeyValue == null) {
208                    result = otherKeyValue == null;
209
210                    // Since our hash code was calculated from the original
211                    // non-null referant, the above check breaks the
212                    // hash code/equals contract, as two cleared Referenced
213                    // objects could test equal but have different hash codes.
214                    // We can reduce (not eliminate) the chance of this
215                    // happening by comparing hash codes.
216                    result = result && hashCode() == otherKey.hashCode();
217                    // In any case, as our c'tor does not allow null referants
218                    // and Hashtable does not do equality checks between
219                    // existing keys, normal hash table operations should never
220                    // result in an equals comparison between null referants
221                }
222                else
223                {
224                    result = thisKeyValue.equals(otherKeyValue);
225                }
226            }
227            return result;
228        }
229
230        private Object getValue() {
231            return reference.get();
232        }
233
234        @Override
235        public int hashCode() {
236            return hashCode;
237        }
238    }
239
240    /**
241     * WeakReference subclass that holds a hard reference to an
242     * associated {@code value} and also makes accessible
243     * the Referenced object holding it.
244     */
245    private final static class WeakKey extends WeakReference {
246
247        private final Referenced referenced;
248
249        private WeakKey(final Object key,
250                        final ReferenceQueue queue,
251                        final Referenced referenced) {
252            super(key, queue);
253            this.referenced = referenced;
254        }
255
256        private Referenced getReferenced() {
257            return referenced;
258        }
259     }
260
261    /** Serializable version identifier. */
262    private static final long serialVersionUID = -1546036869799732453L;
263
264    /**
265     * The maximum number of times put() or remove() can be called before
266     * the map will be purged of all cleared entries.
267     */
268    private static final int MAX_CHANGES_BEFORE_PURGE = 100;
269
270    /**
271     * The maximum number of times put() or remove() can be called before
272     * the map will be purged of one cleared entry.
273     */
274    private static final int PARTIAL_PURGE_COUNT     = 10;
275
276    /** ReferenceQueue we check for GC'd keys. */
277    private final transient ReferenceQueue queue = new ReferenceQueue();
278
279    /** Counter used to control how often we purge gc'd entries. */
280    private int changeCount;
281
282    /**
283     * Constructs a WeakHashtable with the Hashtable default
284     * capacity and load factor.
285     */
286    public WeakHashtable() {}
287
288    /**
289     *@see Hashtable
290     */
291    @Override
292    public boolean containsKey(final Object key) {
293        // purge should not be required
294        final Referenced referenced = new Referenced(key);
295        return super.containsKey(referenced);
296    }
297
298    /**
299     *@see Hashtable
300     */
301    @Override
302    public Enumeration elements() {
303        purge();
304        return super.elements();
305    }
306
307    /**
308     *@see Hashtable
309     */
310    @Override
311    public Set entrySet() {
312        purge();
313        final Set referencedEntries = super.entrySet();
314        final Set unreferencedEntries = new HashSet();
315        for (final Object referencedEntry : referencedEntries) {
316            final Map.Entry entry = (Map.Entry) referencedEntry;
317            final Referenced referencedKey = (Referenced) entry.getKey();
318            final Object key = referencedKey.getValue();
319            final Object value = entry.getValue();
320            if (key != null) {
321                final Entry dereferencedEntry = new Entry(key, value);
322                unreferencedEntries.add(dereferencedEntry);
323            }
324        }
325        return unreferencedEntries;
326    }
327
328    /**
329     *@see Hashtable
330     */
331    @Override
332    public Object get(final Object key) {
333        // for performance reasons, no purge
334        final Referenced referenceKey = new Referenced(key);
335        return super.get(referenceKey);
336    }
337
338    /**
339     *@see Hashtable
340     */
341    @Override
342    public boolean isEmpty() {
343        purge();
344        return super.isEmpty();
345    }
346
347    /**
348     *@see Hashtable
349     */
350    @Override
351    public Enumeration keys() {
352        purge();
353        final Enumeration enumer = super.keys();
354        return new Enumeration() {
355            @Override
356            public boolean hasMoreElements() {
357                return enumer.hasMoreElements();
358            }
359            @Override
360            public Object nextElement() {
361                 final Referenced nextReference = (Referenced) enumer.nextElement();
362                 return nextReference.getValue();
363            }
364        };
365    }
366
367    /**
368     *@see Hashtable
369     */
370    @Override
371    public Set keySet() {
372        purge();
373        final Set referencedKeys = super.keySet();
374        final Set unreferencedKeys = new HashSet();
375        for (final Object referencedKey : referencedKeys) {
376            final Referenced referenceKey = (Referenced) referencedKey;
377            final Object keyValue = referenceKey.getValue();
378            if (keyValue != null) {
379                unreferencedKeys.add(keyValue);
380            }
381        }
382        return unreferencedKeys;
383    }
384
385    /**
386     * Purges all entries whose wrapped keys
387     * have been garbage collected.
388     */
389    private void purge() {
390        final List toRemove = new ArrayList();
391        synchronized (queue) {
392            WeakKey key;
393            while ((key = (WeakKey) queue.poll()) != null) {
394                toRemove.add(key.getReferenced());
395            }
396        }
397
398        // LOGGING-119: do the actual removal of the keys outside the sync block
399        // to prevent deadlock scenarios as purge() may be called from
400        // non-synchronized methods too
401        final int size = toRemove.size();
402        for (int i = 0; i < size; i++) {
403            super.remove(toRemove.get(i));
404        }
405    }
406
407    /**
408     * Purges one entry whose wrapped key
409     * has been garbage collected.
410     */
411    private void purgeOne() {
412        synchronized (queue) {
413            final WeakKey key = (WeakKey) queue.poll();
414            if (key != null) {
415                super.remove(key.getReferenced());
416            }
417        }
418    }
419
420    /**
421     *@see Hashtable
422     */
423    @Override
424    public synchronized Object put(final Object key, final Object value) {
425        // check for nulls, ensuring semantics match superclass
426        Objects.requireNonNull(key, "key");
427        Objects.requireNonNull(value, "value");
428
429        // for performance reasons, only purge every
430        // MAX_CHANGES_BEFORE_PURGE times
431        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
432            purge();
433            changeCount = 0;
434        }
435        // do a partial purge more often
436        else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
437            purgeOne();
438        }
439
440        final Referenced keyRef = new Referenced(key, queue);
441        return super.put(keyRef, value);
442    }
443
444    /**
445     *@see Hashtable
446     */
447    @Override
448    public void putAll(final Map t) {
449        if (t != null) {
450            final Set entrySet = t.entrySet();
451            for (final Object element : entrySet) {
452                final Map.Entry entry = (Map.Entry) element;
453                put(entry.getKey(), entry.getValue());
454            }
455        }
456    }
457
458    /**
459     * @see Hashtable
460     */
461    @Override
462    protected void rehash() {
463        // purge here to save the effort of rehashing dead entries
464        purge();
465        super.rehash();
466    }
467
468    /**
469     *@see Hashtable
470     */
471    @Override
472    public synchronized Object remove(final Object key) {
473        // for performance reasons, only purge every
474        // MAX_CHANGES_BEFORE_PURGE times
475        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
476            purge();
477            changeCount = 0;
478        }
479        // do a partial purge more often
480        else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
481            purgeOne();
482        }
483        return super.remove(new Referenced(key));
484    }
485
486    /**
487     *@see Hashtable
488     */
489    @Override
490    public int size() {
491        purge();
492        return super.size();
493    }
494
495    /**
496     *@see Hashtable
497     */
498    @Override
499    public String toString() {
500        purge();
501        return super.toString();
502    }
503
504    /**
505     *@see Hashtable
506     */
507    @Override
508    public Collection values() {
509        purge();
510        return super.values();
511    }
512}