Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright 2004 The Apache Software Foundation.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package org.apache.commons.logging.impl;
     19 
     20 import java.lang.ref.ReferenceQueue;
     21 import java.lang.ref.WeakReference;
     22 import java.util.*;
     23 
     24 /**
     25  * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
     26  * to hold its keys thus allowing them to be reclaimed by the garbage collector.
     27  * The associated values are retained using strong references.</p>
     28  *
     29  * <p>This class follows the symantics of <code>Hashtable</code> as closely as
     30  * possible. It therefore does not accept null values or keys.</p>
     31  *
     32  * <p><strong>Note:</strong>
     33  * This is <em>not</em> intended to be a general purpose hash table replacement.
     34  * This implementation is also tuned towards a particular purpose: for use as a replacement
     35  * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires
     36  * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs
     37  * have been made with this in mind.
     38  * </p>
     39  * <p>
     40  * <strong>Usage:</strong> typical use case is as a drop-in replacement
     41  * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments
     42  * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
     43  * allow classloaders to be collected by the garbage collector without the need
     44  * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
     45  * </p>
     46  *
     47  * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class
     48  * can be supported by the current JVM, and if so then uses it to store
     49  * references to the <code>LogFactory</code> implementationd it loads
     50  * (rather than using a standard Hashtable instance).
     51  * Having this class used instead of <code>Hashtable</code> solves
     52  * certain issues related to dynamic reloading of applications in J2EE-style
     53  * environments. However this class requires java 1.3 or later (due to its use
     54  * of <code>java.lang.ref.WeakReference</code> and associates).
     55  * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code>
     56  * for backwards compatibility reasons. See the documentation
     57  * for method <code>LogFactory.createFactoryStore</code> for more details.</p>
     58  *
     59  * <p>The reason all this is necessary is due to a issue which
     60  * arises during hot deploy in a J2EE-like containers.
     61  * Each component running in the container owns one or more classloaders; when
     62  * the component loads a LogFactory instance via the component classloader
     63  * a reference to it gets stored in the static LogFactory.factories member,
     64  * keyed by the component's classloader so different components don't
     65  * stomp on each other. When the component is later unloaded, the container
     66  * sets the component's classloader to null with the intent that all the
     67  * component's classes get garbage-collected. However there's still a
     68  * reference to the component's classloader from a key in the "global"
     69  * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code>
     70  * is called whenever component is unloaded, the classloaders will be correctly
     71  * garbage collected; this <i>should</i> be done by any container that
     72  * bundles commons-logging by default. However, holding the classloader
     73  * references weakly ensures that the classloader will be garbage collected
     74  * without the container performing this step. </p>
     75  *
     76  * <p>
     77  * <strong>Limitations:</strong>
     78  * There is still one (unusual) scenario in which a component will not
     79  * be correctly unloaded without an explicit release. Though weak references
     80  * are used for its keys, it is necessary to use strong references for its values.
     81  * </p>
     82  *
     83  * <p> If the abstract class <code>LogFactory</code> is
     84  * loaded by the container classloader but a subclass of
     85  * <code>LogFactory</code> [LogFactory1] is loaded by the component's
     86  * classloader and an instance stored in the static map associated with the
     87  * base LogFactory class, then there is a strong reference from the LogFactory
     88  * class to the LogFactory1 instance (as normal) and a strong reference from
     89  * the LogFactory1 instance to the component classloader via
     90  * <code>getClass().getClassLoader()</code>. This chain of references will prevent
     91  * collection of the child classloader.</p>
     92  *
     93  * <p>
     94  * Such a situation occurs when the commons-logging.jar is
     95  * loaded by a parent classloader (e.g. a server level classloader in a
     96  * servlet container) and a custom <code>LogFactory</code> implementation is
     97  * loaded by a child classloader (e.g. a web app classloader).</p>
     98  *
     99  * <p>To avoid this scenario, ensure
    100  * that any custom LogFactory subclass is loaded by the same classloader as
    101  * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is,
    102  * however, rare. The standard LogFactoryImpl class should be sufficient
    103  * for most or all users.</p>
    104  *
    105  *
    106  * @author Brian Stansberry
    107  *
    108  * @since 1.1
    109  *
    110  * @deprecated Please use {@link java.net.URL#openConnection} instead.
    111  *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
    112  *     for further details.
    113  */
    114 @Deprecated
    115 public final class WeakHashtable extends Hashtable {
    116 
    117     /**
    118      * The maximum number of times put() or remove() can be called before
    119      * the map will be purged of all cleared entries.
    120      */
    121     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
    122 
    123     /**
    124      * The maximum number of times put() or remove() can be called before
    125      * the map will be purged of one cleared entry.
    126      */
    127     private static final int PARTIAL_PURGE_COUNT     = 10;
    128 
    129     /* ReferenceQueue we check for gc'd keys */
    130     private ReferenceQueue queue = new ReferenceQueue();
    131     /* Counter used to control how often we purge gc'd entries */
    132     private int changeCount = 0;
    133 
    134     /**
    135      * Constructs a WeakHashtable with the Hashtable default
    136      * capacity and load factor.
    137      */
    138     public WeakHashtable() {}
    139 
    140 
    141     /**
    142      *@see Hashtable
    143      */
    144     public boolean containsKey(Object key) {
    145         // purge should not be required
    146         Referenced referenced = new Referenced(key);
    147         return super.containsKey(referenced);
    148     }
    149 
    150     /**
    151      *@see Hashtable
    152      */
    153     public Enumeration elements() {
    154         purge();
    155         return super.elements();
    156     }
    157 
    158     /**
    159      *@see Hashtable
    160      */
    161     public Set entrySet() {
    162         purge();
    163         Set referencedEntries = super.entrySet();
    164         Set unreferencedEntries = new HashSet();
    165         for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
    166             Map.Entry entry = (Map.Entry) it.next();
    167             Referenced referencedKey = (Referenced) entry.getKey();
    168             Object key = referencedKey.getValue();
    169             Object value = entry.getValue();
    170             if (key != null) {
    171                 Entry dereferencedEntry = new Entry(key, value);
    172                 unreferencedEntries.add(dereferencedEntry);
    173             }
    174         }
    175         return unreferencedEntries;
    176     }
    177 
    178     /**
    179      *@see Hashtable
    180      */
    181     public Object get(Object key) {
    182         // for performance reasons, no purge
    183         Referenced referenceKey = new Referenced(key);
    184         return super.get(referenceKey);
    185     }
    186 
    187     /**
    188      *@see Hashtable
    189      */
    190     public Enumeration keys() {
    191         purge();
    192         final Enumeration enumer = super.keys();
    193         return new Enumeration() {
    194             public boolean hasMoreElements() {
    195                 return enumer.hasMoreElements();
    196             }
    197             public Object nextElement() {
    198                  Referenced nextReference = (Referenced) enumer.nextElement();
    199                  return nextReference.getValue();
    200             }
    201         };
    202     }
    203 
    204 
    205     /**
    206      *@see Hashtable
    207      */
    208     public Set keySet() {
    209         purge();
    210         Set referencedKeys = super.keySet();
    211         Set unreferencedKeys = new HashSet();
    212         for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
    213             Referenced referenceKey = (Referenced) it.next();
    214             Object keyValue = referenceKey.getValue();
    215             if (keyValue != null) {
    216                 unreferencedKeys.add(keyValue);
    217             }
    218         }
    219         return unreferencedKeys;
    220     }
    221 
    222     /**
    223      *@see Hashtable
    224      */
    225     public Object put(Object key, Object value) {
    226         // check for nulls, ensuring symantics match superclass
    227         if (key == null) {
    228             throw new NullPointerException("Null keys are not allowed");
    229         }
    230         if (value == null) {
    231             throw new NullPointerException("Null values are not allowed");
    232         }
    233 
    234         // for performance reasons, only purge every
    235         // MAX_CHANGES_BEFORE_PURGE times
    236         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
    237             purge();
    238             changeCount = 0;
    239         }
    240         // do a partial purge more often
    241         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
    242             purgeOne();
    243         }
    244 
    245         Object result = null;
    246         Referenced keyRef = new Referenced(key, queue);
    247         return super.put(keyRef, value);
    248     }
    249 
    250     /**
    251      *@see Hashtable
    252      */
    253     public void putAll(Map t) {
    254         if (t != null) {
    255             Set entrySet = t.entrySet();
    256             for (Iterator it=entrySet.iterator(); it.hasNext();) {
    257                 Map.Entry entry = (Map.Entry) it.next();
    258                 put(entry.getKey(), entry.getValue());
    259             }
    260         }
    261     }
    262 
    263     /**
    264      *@see Hashtable
    265      */
    266     public Collection values() {
    267         purge();
    268         return super.values();
    269     }
    270 
    271     /**
    272      *@see Hashtable
    273      */
    274     public Object remove(Object key) {
    275         // for performance reasons, only purge every
    276         // MAX_CHANGES_BEFORE_PURGE times
    277         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
    278             purge();
    279             changeCount = 0;
    280         }
    281         // do a partial purge more often
    282         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
    283             purgeOne();
    284         }
    285         return super.remove(new Referenced(key));
    286     }
    287 
    288     /**
    289      *@see Hashtable
    290      */
    291     public boolean isEmpty() {
    292         purge();
    293         return super.isEmpty();
    294     }
    295 
    296     /**
    297      *@see Hashtable
    298      */
    299     public int size() {
    300         purge();
    301         return super.size();
    302     }
    303 
    304     /**
    305      *@see Hashtable
    306      */
    307     public String toString() {
    308         purge();
    309         return super.toString();
    310     }
    311 
    312     /**
    313      * @see Hashtable
    314      */
    315     protected void rehash() {
    316         // purge here to save the effort of rehashing dead entries
    317         purge();
    318         super.rehash();
    319     }
    320 
    321     /**
    322      * Purges all entries whose wrapped keys
    323      * have been garbage collected.
    324      */
    325     private void purge() {
    326         synchronized (queue) {
    327             WeakKey key;
    328             while ((key = (WeakKey) queue.poll()) != null) {
    329                 super.remove(key.getReferenced());
    330             }
    331         }
    332     }
    333 
    334     /**
    335      * Purges one entry whose wrapped key
    336      * has been garbage collected.
    337      */
    338     private void purgeOne() {
    339 
    340         synchronized (queue) {
    341             WeakKey key = (WeakKey) queue.poll();
    342             if (key != null) {
    343                 super.remove(key.getReferenced());
    344             }
    345         }
    346     }
    347 
    348     /** Entry implementation */
    349     private final static class Entry implements Map.Entry {
    350 
    351         private final Object key;
    352         private final Object value;
    353 
    354         private Entry(Object key, Object value) {
    355             this.key = key;
    356             this.value = value;
    357         }
    358 
    359         public boolean equals(Object o) {
    360             boolean result = false;
    361             if (o != null && o instanceof Map.Entry) {
    362                 Map.Entry entry = (Map.Entry) o;
    363                 result =    (getKey()==null ?
    364                                             entry.getKey() == null :
    365                                             getKey().equals(entry.getKey()))
    366                             &&
    367                             (getValue()==null ?
    368                                             entry.getValue() == null :
    369                                             getValue().equals(entry.getValue()));
    370             }
    371             return result;
    372         }
    373 
    374         public int hashCode() {
    375 
    376             return (getKey()==null ? 0 : getKey().hashCode()) ^
    377                 (getValue()==null ? 0 : getValue().hashCode());
    378         }
    379 
    380         public Object setValue(Object value) {
    381             throw new UnsupportedOperationException("Entry.setValue is not supported.");
    382         }
    383 
    384         public Object getValue() {
    385             return value;
    386         }
    387 
    388         public Object getKey() {
    389             return key;
    390         }
    391     }
    392 
    393 
    394     /** Wrapper giving correct symantics for equals and hashcode */
    395     private final static class Referenced {
    396 
    397         private final WeakReference reference;
    398         private final int           hashCode;
    399 
    400         /**
    401          *
    402          * @throws NullPointerException if referant is <code>null</code>
    403          */
    404         private Referenced(Object referant) {
    405             reference = new WeakReference(referant);
    406             // Calc a permanent hashCode so calls to Hashtable.remove()
    407             // work if the WeakReference has been cleared
    408             hashCode  = referant.hashCode();
    409         }
    410 
    411         /**
    412          *
    413          * @throws NullPointerException if key is <code>null</code>
    414          */
    415         private Referenced(Object key, ReferenceQueue queue) {
    416             reference = new WeakKey(key, queue, this);
    417             // Calc a permanent hashCode so calls to Hashtable.remove()
    418             // work if the WeakReference has been cleared
    419             hashCode  = key.hashCode();
    420 
    421         }
    422 
    423         public int hashCode() {
    424             return hashCode;
    425         }
    426 
    427         private Object getValue() {
    428             return reference.get();
    429         }
    430 
    431         public boolean equals(Object o) {
    432             boolean result = false;
    433             if (o instanceof Referenced) {
    434                 Referenced otherKey = (Referenced) o;
    435                 Object thisKeyValue = getValue();
    436                 Object otherKeyValue = otherKey.getValue();
    437                 if (thisKeyValue == null) {
    438                     result = (otherKeyValue == null);
    439 
    440                     // Since our hashcode was calculated from the original
    441                     // non-null referant, the above check breaks the
    442                     // hashcode/equals contract, as two cleared Referenced
    443                     // objects could test equal but have different hashcodes.
    444                     // We can reduce (not eliminate) the chance of this
    445                     // happening by comparing hashcodes.
    446                     if (result == true) {
    447                         result = (this.hashCode() == otherKey.hashCode());
    448                     }
    449                     // In any case, as our c'tor does not allow null referants
    450                     // and Hashtable does not do equality checks between
    451                     // existing keys, normal hashtable operations should never
    452                     // result in an equals comparison between null referants
    453                 }
    454                 else
    455                 {
    456                     result = thisKeyValue.equals(otherKeyValue);
    457                 }
    458             }
    459             return result;
    460         }
    461     }
    462 
    463     /**
    464      * WeakReference subclass that holds a hard reference to an
    465      * associated <code>value</code> and also makes accessible
    466      * the Referenced object holding it.
    467      */
    468     private final static class WeakKey extends WeakReference {
    469 
    470         private final Referenced referenced;
    471 
    472         private WeakKey(Object key,
    473                         ReferenceQueue queue,
    474                         Referenced referenced) {
    475             super(key, queue);
    476             this.referenced = referenced;
    477         }
    478 
    479         private Referenced getReferenced() {
    480             return referenced;
    481         }
    482      }
    483 }
    484