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); + } +}