/* * Copyright 2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.logging.impl; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.*; /** *
Implementation of Hashtable
that uses WeakReference
's
* to hold its keys thus allowing them to be reclaimed by the garbage collector.
* The associated values are retained using strong references.
This class follows the symantics of Hashtable
as closely as
* possible. It therefore does not accept null values or keys.
Note:
* This is not intended to be a general purpose hash table replacement.
* This implementation is also tuned towards a particular purpose: for use as a replacement
* for Hashtable
in LogFactory
. This application requires
* good liveliness for get
and put
. Various tradeoffs
* have been made with this in mind.
*
* Usage: typical use case is as a drop-in replacement
* for the Hashtable
used in LogFactory
for J2EE enviroments
* running 1.3+ JVMs. Use of this class in most cases (see below) will
* allow classloaders to be collected by the garbage collector without the need
* to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
*
org.apache.commons.logging.LogFactory
checks whether this class
* can be supported by the current JVM, and if so then uses it to store
* references to the LogFactory
implementationd it loads
* (rather than using a standard Hashtable instance).
* Having this class used instead of Hashtable
solves
* certain issues related to dynamic reloading of applications in J2EE-style
* environments. However this class requires java 1.3 or later (due to its use
* of java.lang.ref.WeakReference
and associates).
* And by the way, this extends Hashtable
rather than HashMap
* for backwards compatibility reasons. See the documentation
* for method LogFactory.createFactoryStore
for more details.
The reason all this is necessary is due to a issue which
* arises during hot deploy in a J2EE-like containers.
* Each component running in the container owns one or more classloaders; when
* the component loads a LogFactory instance via the component classloader
* a reference to it gets stored in the static LogFactory.factories member,
* keyed by the component's classloader so different components don't
* stomp on each other. When the component is later unloaded, the container
* sets the component's classloader to null with the intent that all the
* component's classes get garbage-collected. However there's still a
* reference to the component's classloader from a key in the "global"
* LogFactory
's factories member! If LogFactory.release()
* is called whenever component is unloaded, the classloaders will be correctly
* garbage collected; this should be done by any container that
* bundles commons-logging by default. However, holding the classloader
* references weakly ensures that the classloader will be garbage collected
* without the container performing this step.
* Limitations: * There is still one (unusual) scenario in which a component will not * be correctly unloaded without an explicit release. Though weak references * are used for its keys, it is necessary to use strong references for its values. *
* * If the abstract class LogFactory
is
* loaded by the container classloader but a subclass of
* LogFactory
[LogFactory1] is loaded by the component's
* classloader and an instance stored in the static map associated with the
* base LogFactory class, then there is a strong reference from the LogFactory
* class to the LogFactory1 instance (as normal) and a strong reference from
* the LogFactory1 instance to the component classloader via
* getClass().getClassLoader()
. This chain of references will prevent
* collection of the child classloader.
* Such a situation occurs when the commons-logging.jar is
* loaded by a parent classloader (e.g. a server level classloader in a
* servlet container) and a custom LogFactory
implementation is
* loaded by a child classloader (e.g. a web app classloader).
To avoid this scenario, ensure
* that any custom LogFactory subclass is loaded by the same classloader as
* the base LogFactory
. Creating custom LogFactory subclasses is,
* however, rare. The standard LogFactoryImpl class should be sufficient
* for most or all users.
null
*/
private Referenced(Object referant) {
reference = new WeakReference(referant);
// Calc a permanent hashCode so calls to Hashtable.remove()
// work if the WeakReference has been cleared
hashCode = referant.hashCode();
}
/**
*
* @throws NullPointerException if key is null
*/
private Referenced(Object key, ReferenceQueue queue) {
reference = new WeakKey(key, queue, this);
// Calc a permanent hashCode so calls to Hashtable.remove()
// work if the WeakReference has been cleared
hashCode = key.hashCode();
}
public int hashCode() {
return hashCode;
}
private Object getValue() {
return reference.get();
}
public boolean equals(Object o) {
boolean result = false;
if (o instanceof Referenced) {
Referenced otherKey = (Referenced) o;
Object thisKeyValue = getValue();
Object otherKeyValue = otherKey.getValue();
if (thisKeyValue == null) {
result = (otherKeyValue == null);
// Since our hashcode was calculated from the original
// non-null referant, the above check breaks the
// hashcode/equals contract, as two cleared Referenced
// objects could test equal but have different hashcodes.
// We can reduce (not eliminate) the chance of this
// happening by comparing hashcodes.
if (result == true) {
result = (this.hashCode() == otherKey.hashCode());
}
// In any case, as our c'tor does not allow null referants
// and Hashtable does not do equality checks between
// existing keys, normal hashtable operations should never
// result in an equals comparison between null referants
}
else
{
result = thisKeyValue.equals(otherKeyValue);
}
}
return result;
}
}
/**
* WeakReference subclass that holds a hard reference to an
* associated value
and also makes accessible
* the Referenced object holding it.
*/
private final static class WeakKey extends WeakReference {
private final Referenced referenced;
private WeakKey(Object key,
ReferenceQueue queue,
Referenced referenced) {
super(key, queue);
this.referenced = referenced;
}
private Referenced getReferenced() {
return referenced;
}
}
}