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 */ WeakHashtable()138 public WeakHashtable() {} 139 140 141 /** 142 *@see Hashtable 143 */ containsKey(Object key)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 */ elements()153 public Enumeration elements() { 154 purge(); 155 return super.elements(); 156 } 157 158 /** 159 *@see Hashtable 160 */ entrySet()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 */ get(Object key)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 */ keys()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