diff --git a/src/test/org/apache/commons/logging/pathable/ChildFirstTestCase.java b/src/test/org/apache/commons/logging/pathable/ChildFirstTestCase.java
new file mode 100644
index 0000000..d46f700
--- /dev/null
+++ b/src/test/org/apache/commons/logging/pathable/ChildFirstTestCase.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2005 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.pathable;
+
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.ArrayList;
+import java.net.URLClassLoader;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.logging.PathableTestSuite;
+import org.apache.commons.logging.PathableClassLoader;
+
+/**
+ * Tests for the PathableTestSuite and PathableClassLoader functionality,
+ * where lookup order for the PathableClassLoader is child-first.
+ *
+ * These tests assume:
+ *
+ * - junit is in system classpath
+ *
- nothing else is in system classpath
+ *
+ */
+
+public class ChildFirstTestCase extends TestCase {
+
+ /**
+ * Set up a custom classloader hierarchy for this test case.
+ * The hierarchy is:
+ *
+ * - contextloader: child-first.
+ *
- childloader: child-first, used to load test case.
+ *
- parentloader: child-first, parent is the bootclassloader.
+ *
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = ChildFirstTestCase.class;
+ ClassLoader thisClassLoader = thisClass.getClassLoader();
+
+ // Make the parent a direct child of the bootloader to hide all
+ // other classes in the system classpath
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.setParentFirst(false);
+
+ // Make the junit classes visible as a special case, as junit
+ // won't be able to call this class at all without this. The
+ // junit classes must be visible from the classloader that loaded
+ // this class, so use that as the source for future access to classes
+ // from the junit package.
+ parent.useExplicitLoader("junit.", thisClassLoader);
+
+ // Make the commons-logging.jar classes visible via the parent
+ parent.addLogicalLib("commons-logging");
+
+ // Create a child classloader to load the test case through
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.setParentFirst(false);
+
+ // Obviously, the child classloader needs to have the test classes
+ // in its path!
+ child.addLogicalLib("testclasses");
+ child.addLogicalLib("commons-logging-adapters");
+
+ // Create a third classloader to be the context classloader.
+ PathableClassLoader context = new PathableClassLoader(child);
+ context.setParentFirst(false);
+
+ // reload this class via the child classloader
+ Class testClass = child.loadClass(thisClass.getName());
+
+ // and return our custom TestSuite class
+ return new PathableTestSuite(testClass, context);
+ }
+
+ /**
+ * Test that the classloader hierarchy is as expected, and that
+ * calling loadClass() on various classloaders works as expected.
+ * Note that for this test case, parent-first classloading is
+ * in effect.
+ */
+ public void testPaths() throws Exception {
+ // the context classloader is not expected to be null
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ assertNotNull("Context classloader is null", contextLoader);
+ assertEquals("Context classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ contextLoader.getClass().getName());
+
+ // the classloader that loaded this class is obviously not null
+ ClassLoader thisLoader = this.getClass().getClassLoader();
+ assertNotNull("thisLoader is null", thisLoader);
+ assertEquals("thisLoader has unexpected type",
+ PathableClassLoader.class.getName(),
+ thisLoader.getClass().getName());
+
+ // the suite method specified that the context classloader's parent
+ // is the loader that loaded this test case.
+ assertSame("Context classloader is not child of thisLoader",
+ thisLoader, contextLoader.getParent());
+
+ // thisLoader's parent should be available
+ ClassLoader parentLoader = thisLoader.getParent();
+ assertNotNull("Parent classloader is null", parentLoader);
+ assertEquals("Parent classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ parentLoader.getClass().getName());
+
+ // parent should have a parent of null
+ assertNull("Parent classloader has non-null parent", parentLoader.getParent());
+
+ // getSystemClassloader is not a PathableClassLoader; it's of a
+ // built-in type. This also verifies that system classloader is none of
+ // (context, child, parent).
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+ assertNotNull("System classloader is null", systemLoader);
+ assertFalse("System classloader has unexpected type",
+ PathableClassLoader.class.getName().equals(
+ systemLoader.getClass().getName()));
+
+ // junit classes should be visible; their classloader is system.
+ // this will of course throw an exception if not found.
+ Class junitTest = contextLoader.loadClass("junit.framework.Test");
+ assertSame("Junit not loaded via systemloader",
+ systemLoader, junitTest.getClassLoader());
+
+ // jcl api classes should be visible only via the parent
+ Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
+ assertSame("Log class not loaded via parent",
+ logClass.getClassLoader(), parentLoader);
+
+ // jcl adapter classes should be visible via both parent and child. However
+ // as the classloaders are child-first we should see the child one.
+ Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4J12Logger");
+ assertSame("Log4J12Logger not loaded via child",
+ log4jClass.getClassLoader(), thisLoader);
+
+ // test classes should be visible via the child only
+ Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
+ assertSame("PathableTestSuite not loaded via child",
+ testClass.getClassLoader(), thisLoader);
+
+ // test loading of class that is not available
+ try {
+ Class noSuchClass = contextLoader.loadClass("no.such.class");
+ fail("Class no.such.class is unexpectedly available");
+ } catch(ClassNotFoundException ex) {
+ // ok
+ }
+
+ // String class classloader is null
+ Class stringClass = contextLoader.loadClass("java.lang.String");
+ assertNull("String class classloader is not null!",
+ stringClass.getClassLoader());
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResource work as expected.
+ */
+ public void testResource() {
+ URL resource;
+
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+
+ // getResource where it doesn't exist
+ resource = childLoader.getResource("nosuchfile");
+ assertNull("Non-null URL returned for invalid resource name", resource);
+
+ // getResource where it is accessable only to parent classloader
+ resource = childLoader.getResource("org/apache/commons/logging/Log.class");
+ assertNotNull("Unable to locate Log.class resource", resource);
+
+ // getResource where it is accessable only to child classloader
+ resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
+ assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
+
+ // getResource where it is accessable to both classloaders. The one visible
+ // to the child should be returned. The URL returned will be of form
+ // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
+ // of form commons-logging-adapters-nnnn.jar, not commons-logging-nnnn.jar
+ resource = childLoader.getResource("org/apache/commons/logging/impl/Log4J12Logger.class");
+ assertNotNull("Unable to locate Log4J12Logger.class resource", resource);
+ assertTrue("Incorrect source for Log4J12Logger class",
+ resource.toString().indexOf("/commons-logging-adapters-1.") > 0);
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResources work as expected.
+ */
+ public void testResources() throws Exception {
+ Enumeration resources;
+ URL[] urls;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResources where no instances exist
+ resources = childLoader.getResources("nosuchfile");
+ urls = toURLArray(resources);
+ assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
+
+ // getResources where the resource only exists in the parent
+ resources = childLoader.getResources("org/apache/commons/logging/Log.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
+
+ // getResources where the resource only exists in the child
+ resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
+
+ // getResources where the resource exists in both.
+ // resources should be returned in order (child-resource, parent-resource)
+ resources = childLoader.getResources("org/apache/commons/logging/impl/Log4J12Logger.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log4J12Logger.class resources found", 2, urls.length);
+ assertTrue("Incorrect source for Log4J12Logger class",
+ urls[0].toString().indexOf("/commons-logging-adapters-1.") > 0);
+ assertTrue("Incorrect source for Log4J12Logger class",
+ urls[1].toString().indexOf("/commons-logging-1.") > 0);
+
+ }
+
+ /**
+ * Utility method to convert an enumeration-of-URLs into an array of URLs.
+ */
+ private static URL[] toURLArray(Enumeration e) {
+ ArrayList l = new ArrayList();
+ while (e.hasMoreElements()) {
+ URL u = (URL) e.nextElement();
+ l.add(u);
+ }
+ URL[] tmp = new URL[l.size()];
+ return (URL[]) l.toArray(tmp);
+ }
+
+ /**
+ * Test that getResourceAsStream works.
+ */
+ public void testResourceAsStream() throws Exception {
+ java.io.InputStream is;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResourceAsStream where no instances exist
+ is = childLoader.getResourceAsStream("nosuchfile");
+ assertNull("Invalid resource returned non-null stream", is);
+
+ // getResourceAsStream where resource does exist
+ is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
+ assertNotNull("Null returned for valid resource", is);
+ is.close();
+
+ // It would be nice to test parent-first ordering here, but that would require
+ // having a resource with the same name in both the parent and child loaders,
+ // but with different contents. That's a little tricky to set up so we'll
+ // skip that for now.
+ }
+
+ /**
+ * Verify that the context classloader is a custom one, then reset it to
+ * a non-custom one.
+ */
+ private static void checkAndSetContext() {
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ assertEquals("ContextLoader is of unexpected type",
+ contextLoader.getClass().getName(),
+ PathableClassLoader.class.getName());
+
+ URL[] noUrls = new URL[0];
+ Thread.currentThread().setContextClassLoader(new URLClassLoader(noUrls));
+ }
+
+ /**
+ * Verify that when a test method modifies the context classloader it is
+ * reset before the next test is run.
+ *
+ * This method works in conjunction with testResetContext2. There is no
+ * way of knowing which test method junit will run first, but it doesn't
+ * matter; whichever one of them runs first will modify the contextClassloader.
+ * If the PathableTestSuite isn't resetting the contextClassLoader then whichever
+ * of them runs second will fail. Of course if other methods are run in-between
+ * then those methods might also fail...
+ */
+ public void testResetContext1() {
+ checkAndSetContext();
+ }
+
+ /**
+ * See testResetContext1.
+ */
+ public void testResetContext2() {
+ checkAndSetContext();
+ }
+}