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