diff --git a/build.xml b/build.xml
index ba7360e..fe650bc 100644
--- a/build.xml
+++ b/build.xml
@@ -21,7 +21,7 @@
@@ -462,7 +462,7 @@ limitations under the License.-->'>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/optional/src/java/org/apache/commons/logging/impl/WeakHashtable.java b/optional/src/java/org/apache/commons/logging/impl/WeakHashtable.java
new file mode 100644
index 0000000..99c4309
--- /dev/null
+++ b/optional/src/java/org/apache/commons/logging/impl/WeakHashtable.java
@@ -0,0 +1,283 @@
+/*
+ * 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.WeakReference;
+import java.util.*;
+
+/**
+ *
Implementation of Hashtable that uses WeakReference's
+ * to hold it's keys thus allowing them to be reclaimed by the garbage collector.
+ *
+ *
+ * Usage: typical use case is as a drop-in replacement
+ * for the Hashtable use in LogFactory for J2EE enviroments
+ * running 1.3+ JVMs. Use of this class will allow classloaders to be collected by
+ * the garbage collector without the need to call release.
+ *
+ */
+public final class WeakHashtable extends Hashtable {
+
+
+ public WeakHashtable() {}
+
+ /**
+ *@see Hashtable
+ */
+ public boolean contains(Object value) {
+ if (value instanceof Referenced) {
+ return super.contains(value);
+ }
+ Referenced referenced = new Referenced(value);
+ return super.contains(referenced);
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public boolean containsKey(Object key) {
+ Referenced referenced = new Referenced(key);
+ return super.containsKey(referenced);
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public boolean containsValue(Object value) {
+ if (value instanceof Referenced) {
+ return super.contains(value);
+ }
+ Referenced referenced = new Referenced(value);
+ return super.containsValue(referenced);
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Enumeration elements() {
+ final Enumeration enum = super.elements();
+ return new Enumeration() {
+ public boolean hasMoreElements() {
+ return enum.hasMoreElements();
+ }
+ public Object nextElement() {
+ Referenced nextReference = (Referenced) enum.nextElement();
+ return nextReference.getValue();
+ }
+ };
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Set entrySet() {
+ Set referencedEntries = super.entrySet();
+ Set unreferencedEntries = new HashSet();
+ for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Referenced referencedKey = (Referenced) entry.getKey();
+ Object key = referencedKey.getValue();
+ Referenced referencedValue = (Referenced) entry.getValue();
+ Object value = referencedValue.getValue();
+ if (key != null) {
+ Entry dereferencedEntry = new Entry(key, value);
+ unreferencedEntries.add(dereferencedEntry);
+ }
+ }
+ return unreferencedEntries;
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Object get(Object key) {
+ Object result = null;
+ Referenced referenceKey = new Referenced(key);
+ Referenced referencedValue = (Referenced) super.get(referenceKey);
+ if (referencedValue != null) {
+ result = referencedValue.getValue();
+ }
+ return result;
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Enumeration keys() {
+ final Enumeration enum = super.keys();
+ return new Enumeration() {
+ public boolean hasMoreElements() {
+ return enum.hasMoreElements();
+ }
+ public Object nextElement() {
+ Referenced nextReference = (Referenced) enum.nextElement();
+ return nextReference.getValue();
+ }
+ };
+ }
+
+
+ /**
+ *@see Hashtable
+ */
+ public Set keySet() {
+ Set referencedKeys = super.keySet();
+ Set unreferencedKeys = new HashSet();
+ for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
+ Referenced referenceKey = (Referenced) it.next();
+ Object keyValue = referenceKey.getValue();
+ if (keyValue != null) {
+ unreferencedKeys.add(keyValue);
+ }
+ }
+ return unreferencedKeys;
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Object put(Object key, Object value) {
+ Object result = null;
+ Referenced lastValue = (Referenced) super.put(new Referenced(key), new Referenced(value));
+ if (lastValue != null) {
+ result = lastValue.getValue();
+ }
+ return result;
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public void putAll(Map t) {
+ if (t != null) {
+ Set entrySet = t.entrySet();
+ for (Iterator it=entrySet.iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Collection values() {
+ Collection referencedValues = super.values();
+ ArrayList unreferencedValues = new ArrayList();
+ for (Iterator it = referencedValues.iterator(); it.hasNext();) {
+ Referenced reference = (Referenced) it.next();
+ Object value = reference.getValue();
+ if (value != null) {
+ unreferencedValues.add(value);
+ }
+ }
+ return unreferencedValues;
+ }
+
+ /**
+ *@see Hashtable
+ */
+ public Object remove(Object key) {
+ return super.remove(new Referenced(key));
+ }
+
+ private final static class Entry implements Map.Entry {
+
+ private Object key;
+ private Object value;
+
+ private Entry(Object key, Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public boolean equals(Object o) {
+ boolean result = false;
+ if (o != null && o instanceof Map.Entry) {
+ Map.Entry entry = (Map.Entry) o;
+ result = (getKey()==null ?
+ entry.getKey() == null :
+ getKey().equals(entry.getKey()))
+ &&
+ (getValue()==null ?
+ entry.getValue() == null :
+ getValue().equals(entry.getValue()));
+ }
+ return result;
+ }
+
+ public int hashCode() {
+
+ return (getKey()==null ? 0 : getKey().hashCode()) ^
+ (getValue()==null ? 0 : getValue().hashCode());
+ }
+
+ public Object setValue(Object value) {
+ throw new UnsupportedOperationException("Entry.setValue is not supported.");
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+ }
+
+
+ /** Wrapper giving correct symantics for equals and hashcode */
+ private final static class Referenced {
+
+ private final WeakReference reference;
+
+ private Referenced(Object referant) {
+ reference = new WeakReference(referant);
+ }
+
+ public int hashCode() {
+ int result = 0;
+ Object keyValue = getValue();
+ if (keyValue != null) {
+ result = keyValue.hashCode();
+ }
+ return result;
+ }
+
+ 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);
+ }
+ else
+ {
+ result = thisKeyValue.equals(otherKeyValue);
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/optional/src/test/org/apache/commons/logging/LogFactoryTest.java b/optional/src/test/org/apache/commons/logging/LogFactoryTest.java
new file mode 100644
index 0000000..0633d72
--- /dev/null
+++ b/optional/src/test/org/apache/commons/logging/LogFactoryTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import junit.framework.*;
+import java.util.*;
+import org.apache.commons.logging.impl.WeakHashtable;
+
+public class LogFactoryTest extends TestCase {
+
+
+ /** Maximum number of iterations before our test fails */
+ private static final int MAX_GC_ITERATIONS = 50;
+
+
+ public LogFactoryTest(String testName) {
+ super(testName);
+ }
+
+ public void testLogFactoryType() {
+ assertTrue(LogFactory.factories instanceof WeakHashtable);
+ }
+}
diff --git a/optional/src/test/org/apache/commons/logging/TestAll.java b/optional/src/test/org/apache/commons/logging/TestAll.java
index e0fdcbc..8726e2d 100644
--- a/optional/src/test/org/apache/commons/logging/TestAll.java
+++ b/optional/src/test/org/apache/commons/logging/TestAll.java
@@ -19,13 +19,14 @@ package org.apache.commons.logging;
import junit.framework.*;
import org.apache.commons.logging.impl.MemoryLogTest;
+import org.apache.commons.logging.impl.WeakHashtableTest;
/**
* The build script calls just one TestSuite - this one!
* All tests should be written into separate TestSuite's
* and added to this. Don't clutter this class with implementations.
*
- * @version $Revision: 1.1 $
+ * @version $Revision: 1.2 $
*/
public class TestAll extends TestCase {
@@ -38,6 +39,8 @@ public class TestAll extends TestCase {
TestSuite suite = new TestSuite();
suite.addTest(MemoryLogTest.suite());
+ suite.addTestSuite(WeakHashtableTest.class);
+ suite.addTestSuite(LogFactoryTest.class);
return suite;
}
diff --git a/optional/src/test/org/apache/commons/logging/impl/WeakHashtableTest.java b/optional/src/test/org/apache/commons/logging/impl/WeakHashtableTest.java
new file mode 100644
index 0000000..23ee252
--- /dev/null
+++ b/optional/src/test/org/apache/commons/logging/impl/WeakHashtableTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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 junit.framework.*;
+import java.util.*;
+
+public class WeakHashtableTest extends TestCase {
+
+
+ /** Maximum number of iterations before our test fails */
+ private static final int MAX_GC_ITERATIONS = 50;
+
+ private WeakHashtable weakHashtable;
+ private Long keyOne;
+ private Long keyTwo;
+ private Long keyThree;
+ private Long valueOne;
+ private Long valueTwo;
+ private Long valueThree;
+
+ public WeakHashtableTest(String testName) {
+ super(testName);
+ }
+
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ weakHashtable = new WeakHashtable();
+
+ keyOne = new Long(1);
+ keyTwo = new Long(2);
+ keyThree = new Long(3);
+ valueOne = new Long(100);
+ valueTwo = new Long(200);
+ valueThree = new Long(300);
+
+ weakHashtable.put(keyOne, valueOne);
+ weakHashtable.put(keyTwo, valueTwo);
+ weakHashtable.put(keyThree, valueThree);
+ }
+
+ /** Tests public boolean contains(ObjectÊvalue) */
+ public void testContains() throws Exception {
+ assertFalse(weakHashtable.contains(new Long(1)));
+ assertFalse(weakHashtable.contains(new Long(2)));
+ assertFalse(weakHashtable.contains(new Long(3)));
+ assertTrue(weakHashtable.contains(new Long(100)));
+ assertTrue(weakHashtable.contains(new Long(200)));
+ assertTrue(weakHashtable.contains(new Long(300)));
+ assertFalse(weakHashtable.contains(new Long(400)));
+ }
+
+ /** Tests public boolean containsKey(ObjectÊkey) */
+ public void testContainsKey() throws Exception {
+ assertTrue(weakHashtable.containsKey(new Long(1)));
+ assertTrue(weakHashtable.containsKey(new Long(2)));
+ assertTrue(weakHashtable.containsKey(new Long(3)));
+ assertFalse(weakHashtable.containsKey(new Long(100)));
+ assertFalse(weakHashtable.containsKey(new Long(200)));
+ assertFalse(weakHashtable.containsKey(new Long(300)));
+ assertFalse(weakHashtable.containsKey(new Long(400)));
+ }
+
+ /** Tests public boolean containsValue(ObjectÊvalue) */
+ public void testContainsValue() throws Exception {
+ assertFalse(weakHashtable.containsValue(new Long(1)));
+ assertFalse(weakHashtable.containsValue(new Long(2)));
+ assertFalse(weakHashtable.containsValue(new Long(3)));
+ assertTrue(weakHashtable.containsValue(new Long(100)));
+ assertTrue(weakHashtable.containsValue(new Long(200)));
+ assertTrue(weakHashtable.containsValue(new Long(300)));
+ assertFalse(weakHashtable.containsValue(new Long(400)));
+ }
+
+ /** Tests public Enumeration elements() */
+ public void testElements() throws Exception {
+ ArrayList elements = new ArrayList();
+ for (Enumeration e = weakHashtable.elements(); e.hasMoreElements();) {
+ elements.add(e.nextElement());
+ }
+ assertEquals(3, elements.size());
+ assertTrue(elements.contains(valueOne));
+ assertTrue(elements.contains(valueTwo));
+ assertTrue(elements.contains(valueThree));
+ }
+
+ /** Tests public Set entrySet() */
+ public void testEntrySet() throws Exception {
+ Set entrySet = weakHashtable.entrySet();
+ for (Iterator it = entrySet.iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Object key = entry.getKey();
+ if (keyOne.equals(key)) {
+ assertEquals(valueOne, entry.getValue());
+ } else if (keyTwo.equals(key)) {
+ assertEquals(valueTwo, entry.getValue());
+ } else if (keyThree.equals(key)) {
+ assertEquals(valueThree, entry.getValue());
+ } else {
+ fail("Unexpected key");
+ }
+ }
+ }
+
+ /** Tests public Object get(ObjectÊkey) */
+ public void testGet() throws Exception {
+ assertEquals(valueOne, weakHashtable.get(keyOne));
+ assertEquals(valueTwo, weakHashtable.get(keyTwo));
+ assertEquals(valueThree, weakHashtable.get(keyThree));
+ assertNull(weakHashtable.get(new Long(50)));
+ }
+
+ /** Tests public Enumeration keys() */
+ public void testKeys() throws Exception {
+ ArrayList keys = new ArrayList();
+ for (Enumeration e = weakHashtable.keys(); e.hasMoreElements();) {
+ keys.add(e.nextElement());
+ }
+ assertEquals(3, keys.size());
+ assertTrue(keys.contains(keyOne));
+ assertTrue(keys.contains(keyTwo));
+ assertTrue(keys.contains(keyThree));
+ }
+
+ /** Tests public Set keySet() */
+ public void testKeySet() throws Exception {
+ Set keySet = weakHashtable.keySet();
+ assertEquals(3, keySet.size());
+ assertTrue(keySet.contains(keyOne));
+ assertTrue(keySet.contains(keyTwo));
+ assertTrue(keySet.contains(keyThree));
+ }
+
+ /** Tests public Object put(ObjectÊkey, ObjectÊvalue) */
+ public void testPut() throws Exception {
+ Long anotherKey = new Long(2004);
+ weakHashtable.put(anotherKey, new Long(1066));
+
+ assertEquals(new Long(1066), weakHashtable.get(anotherKey));
+ }
+
+ /** Tests public void putAll(MapÊt) */
+ public void testPutAll() throws Exception {
+ Map newValues = new HashMap();
+ Long newKey = new Long(1066);
+ Long newValue = new Long(1415);
+ newValues.put(newKey, newValue);
+ Long anotherNewKey = new Long(1645);
+ Long anotherNewValue = new Long(1815);
+ newValues.put(anotherNewKey, anotherNewValue);
+ weakHashtable.putAll(newValues);
+
+ assertEquals(5, weakHashtable.size());
+ assertEquals(newValue, weakHashtable.get(newKey));
+ assertEquals(anotherNewValue, weakHashtable.get(anotherNewKey));
+ }
+
+ /** Tests public Object remove(ObjectÊkey) */
+ public void testRemove() throws Exception {
+ weakHashtable.remove(keyOne);
+ assertEquals(2, weakHashtable.size());
+ assertNull(weakHashtable.get(keyOne));
+ }
+
+ /** Tests public Collection values() */
+ public void testValues() throws Exception {
+ Collection values = weakHashtable.values();
+ assertEquals(3, values.size());
+ assertTrue(values.contains(valueOne));
+ assertTrue(values.contains(valueTwo));
+ assertTrue(values.contains(valueThree));
+ }
+
+ public void testRelease() throws Exception {
+
+ // lose our references
+ keyOne = null;
+ keyTwo = null;
+ keyThree = null;
+ valueOne = null;
+ valueTwo = null;
+ valueThree = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+
+ if(weakHashtable.get(new Long(1)) == null) {
+ break;
+
+ } else {
+ // create garbage:
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/commons/logging/LogFactory.java b/src/java/org/apache/commons/logging/LogFactory.java
index b33f1b4..fe07ec1 100644
--- a/src/java/org/apache/commons/logging/LogFactory.java
+++ b/src/java/org/apache/commons/logging/LogFactory.java
@@ -42,7 +42,7 @@ import java.util.Properties;
* @author Craig R. McClanahan
* @author Costin Manolache
* @author Richard A. Sitze
- * @version $Revision: 1.27 $ $Date: 2004/06/06 21:15:12 $
+ * @version $Revision: 1.28 $ $Date: 2004/11/10 22:59:04 $
*/
public abstract class LogFactory {
@@ -79,6 +79,42 @@ public abstract class LogFactory {
protected static final String SERVICE_ID =
"META-INF/services/org.apache.commons.logging.LogFactory";
+ /**
+ * Setting this system property value allows the Hashtable used to store
+ * classloaders to be substituted by an alternative implementation.
+ *
+ *
+ * Note: LogFactory will print:
+ *
+ * [ERROR] LogFactory: Load of custom hashtable failed
+ *
+ * to system error and then continue using a standard Hashtable.
+ *
+ *
+ * Usage: Set this property when Java is invoked
+ * and LogFactory will attempt to load a new instance
+ * of the given implementation class.
+ * For example, running the following ant scriplet:
+ *
+ * <java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}">
+ * ...
+ * <sysproperty
+ * key="org.apache.commons.logging.LogFactory.HashtableImpl"
+ * value="org.apache.commons.logging.AltHashtable"/>
+ * </java>
+ *
+ * will mean that LogFactory will load an instance of
+ * org.apache.commons.logging.AltHashtable.
+ *
+ *
+ * A typical use case is to allow a custom
+ * Hashtable implementation using weak references to be substituted.
+ * This will allow classloaders to be garbage collected without
+ * the need to release them (on 1.3+ JVMs only, of course ;)
+ *
+ */
+ public static final String HASHTABLE_IMPLEMENTATION_PROPERTY =
+ "org.apache.commons.logging.LogFactory.HashtableImpl";
// ----------------------------------------------------------- Constructors
@@ -181,7 +217,28 @@ public abstract class LogFactory {
* The previously constructed LogFactory instances, keyed by
* the ClassLoader with which it was created.
*/
- protected static Hashtable factories = new Hashtable();
+ protected static Hashtable factories = createFactoryStore();
+
+ private static final Hashtable createFactoryStore() {
+ Hashtable result = null;
+ String storeImplementationClass
+ = System.getProperty(HASHTABLE_IMPLEMENTATION_PROPERTY);
+ if (storeImplementationClass == null) {
+ storeImplementationClass = "org.apache.commons.logging.impl.WeakHashtable";
+ }
+ try {
+ Class implementationClass = Class.forName(storeImplementationClass);
+ result = (Hashtable) implementationClass.newInstance();
+
+ } catch (Exception e) {
+ // ignore
+ System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
+ }
+ if (result == null) {
+ result = new Hashtable();
+ }
+ return result;
+ }
// --------------------------------------------------------- Static Methods
diff --git a/src/test/org/apache/commons/logging/AltHashtable.java b/src/test/org/apache/commons/logging/AltHashtable.java
new file mode 100644
index 0000000..c1ba895
--- /dev/null
+++ b/src/test/org/apache/commons/logging/AltHashtable.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import java.util.Hashtable;
+
+public class AltHashtable extends Hashtable {
+
+ public static Object lastKey;
+ public static Object lastValue;
+
+ public Object put(Object key, Object value) {
+ lastKey = key;
+ lastValue = value;
+ return super.put(key, value);
+ }
+}
diff --git a/src/test/org/apache/commons/logging/AltHashtableTest.java b/src/test/org/apache/commons/logging/AltHashtableTest.java
new file mode 100644
index 0000000..8c4f374
--- /dev/null
+++ b/src/test/org/apache/commons/logging/AltHashtableTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import junit.framework.*;
+
+public class AltHashtableTest extends TestCase {
+
+ public AltHashtableTest(String testName) {
+ super(testName);
+ }
+
+ public void testType() {
+ assertTrue(LogFactory.factories instanceof AltHashtable);
+ }
+
+ public void testPutCalled() throws Exception {
+
+ AltHashtable.lastKey = null;
+ AltHashtable.lastValue = null;
+ ClassLoader classLoader = new ClassLoader() {};
+ Thread thread = new Thread(
+ new Runnable() {
+ public void run() {
+ LogFactory.getLog(AltHashtableTest.class);
+ }
+ }
+ );
+ thread.setContextClassLoader(classLoader);
+
+ thread.start();
+ thread.join();
+
+ assertEquals(classLoader, AltHashtable.lastKey);
+ assertNotNull(AltHashtable.lastValue);
+ }
+}
diff --git a/src/test/org/apache/commons/logging/BadHashtablePropertyTest.java b/src/test/org/apache/commons/logging/BadHashtablePropertyTest.java
new file mode 100644
index 0000000..95a0d14
--- /dev/null
+++ b/src/test/org/apache/commons/logging/BadHashtablePropertyTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import junit.framework.*;
+import java.util.Hashtable;
+
+/**
+ * Tests behaviour when the property is misconfigured.
+ */
+public class BadHashtablePropertyTest extends TestCase {
+
+ public BadHashtablePropertyTest(String testName) {
+ super(testName);
+ }
+
+ public void testType() {
+ assertTrue(LogFactory.factories instanceof Hashtable);
+ }
+
+ public void testPutCalled() throws Exception {
+ Log log = LogFactory.getLog(BadHashtablePropertyTest.class);
+ }
+}