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