    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}