/* Copyright 1996-2008 Ariba, Inc. 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. $Id: //ariba/platform/util/core/ariba/util/core/NonCachingClassLoader.java#9 $ */ package ariba.util.core; import ariba.util.log.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; /** Implementation of a ClassLoader that can be used to force reloading of class files from the file system. This is useful for applications that provide API's for clients to write implementations for. If the NonCachingClassLoader is used to load those implementations, then authors of those implementations do not need to restart the application that calls their implementations. For this to work properly, you need to instantiate the class and assign it to a variable that is of the type of a superclass or an interface of the class that is getting loaded. If not you will get a ClassCastException. Contrary to popular belief, classes are not unique within a VM; they are unique within a class loader within a VM. Thus you can create what you think is a singleton class, such as ClassUtil but find that it gets loaded multiple times if you use the NonCachingClassLoader. Each class called ClassUtil is scoped by the class loader instance that loaded it. If you try to compare the two objects, you will get a ClassCastException. The code that calls the loadClass method and then calls ClassUtil.newInstance with the returned class needs to cast the result to an Interface or superclass that was loaded by the classloader that also loaded the code that calls loadClass. If not, you will get a ClassCastException. Reloading a class necessarily creates multiple copies within the VM, each constrained to the scope of the instance of NonCachingClassLoader that loaded it. There are other classes you'll want reloaded by this classloader. For example, if you want to reload config.java.foo.Bar and config.java.foo.FooUtil which contains utils that Bar calls, then you can instantiate a NonCachingClassLoader with the pattern of "/config.S/" (where S stands for *, which I cannot actually type in a javadoc comment or else it will interpret the star slash as the end of this comment). However, you probably do not want ariba.util.core.ClassUtil reloaded. You can prevent that by making sure you choose a pattern that ariba.util.core.ClassUtil does not conform to. Here is an example usage in which DummyClass implements DummyInterface: public class ClassUtilTest { public void classForNameNonCachingTest () { String className = "test.ariba.util.core.DummyClass"; Class c = ClassUtil.classForNameNonCaching(className, "/test.*Class/", false); DummyInterface inst = (DummyInterface)ClassUtil.newInstance(c); Assert.that(inst.alwaysTrue(), "expected true"); } } Furthermore this is the logging you will see: Loading test.ariba.util.core.DummyClass with NonCachingClassLoader Letting java.lang.Object be loaded by primordial class loader Letting test.ariba.util.core.DummyInterface be loaded by primordial class loader See that DummyInterface is loaded by the primordial class loader? That is the same class loader that loaded ClassUtilTest. The following code will throw a ClassCastException: public class ClassUtilTest { public void classForNameNonCachingTest () { String className = "test.ariba.util.core.DummyClass"; Class c = ClassUtil.classForNameNonCaching(className, "/test.*Class/", false); DummyClass inst = (DummyClass)ClassUtil.newInstance(c); Assert.that(inst.alwaysTrue(), "expected true"); } } Loading test.ariba.util.core.DummyClass with NonCachingClassLoader Letting java.lang.Object be loaded by primordial class loader Letting test.ariba.util.core.DummyInterface be loaded by primordial class loader java.lang.ClassCastException: test.ariba.util.core.DummyClass at test.ariba.util.core.ClassUtilTest.classForNameNonCachingTest(ClassUtilTest.java:240) at test.ariba.util.core.ClassUtilTest.runTests(ClassUtilTest.java:54) at test.ariba.util.core.ClassUtilTest.main(ClassUtilTest.java:37) @aribaapi private */ public class NonCachingClassLoader extends ClassLoader { public static final String ClassName = "ariba.util.core.NonCachingClassLoader"; private Map classes = MapUtil.map(); private String pattern; private static final String ClassFileExtension = ".class"; /** Performs initialization for NonCachingClassLoader by calling the super and setting the pattern for the class. The pattern is the pattern that will be used to identify classes that NonCachingClassLoader should always reload from disk. @param pattern for identifying classes to reload */ public NonCachingClassLoader (String pattern) { this(ClassUtil.classForName(ClassName, false), pattern); } /** This is a trick to get the compiler happy and still try to be efficient and not call classForName more than once */ private NonCachingClassLoader (Class myClass, String pattern) { this(myClass == null ? null : myClass.getClassLoader(), pattern); } /** Performs initialization for NonCachingClassLoader by calling the super and setting the pattern for the class. The pattern is the pattern that will be used to identify classes that NonCachingClassLoader should always reload from disk. @param parent parent class loader used for delegation @param pattern for identifying classes to reload */ public NonCachingClassLoader (ClassLoader parent, String pattern) { super(parent == null ? getSystemClassLoader() : parent); this.pattern = pattern; } /** Simple version of loadClass for external clients since they will always want the class resolved before it is returned to them. @param className the name of the desired Class */ public synchronized Class loadClass (String className) throws ClassNotFoundException { return (loadClass(className, true)); } /** Main loadClass method for loading a class and forcing a reload of the class from disk if the class name conforms to the pattern set as the 'pattern' for the class. @param className the name of the desired Class @param resolveIt true if the Class needs to be resolved */ public synchronized Class loadClass (String className, boolean resolveIt) throws ClassNotFoundException { Class result; // Check our local cache of classes (required to prevent // ClassCastExceptions) result = (Class)classes.get(className); if (result != null) { Log.util.debug("Got %s from classes map cache", className); return result; } if (StringUtil.stringMatchesPattern(className, pattern)) { Log.util.debug("Loading %s with NonCachingClassLoader", className); byte classData[] = null; try { String resourceName = StringUtil.replaceCharByString(className, '.', "/"); resourceName = StringUtil.strcat(resourceName, ClassFileExtension); InputStream in = getResourceAsStream(resourceName); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtil.inputStreamToOutputStream(in, baos); classData = baos.toByteArray(); in.close(); } catch (IOException ioe) { throw new ClassNotFoundException(Fmt.S("Could not read class file: %s", ioe)); } if (classData == null) { throw new ClassNotFoundException(className); } result = defineClass(className, classData, 0, classData.length); if (result == null) { throw new ClassFormatError(); } if (resolveIt) { resolveClass(result); } classes.put(className, result); return result; } else { Log.util.debug("Letting %s be loaded by primordial class loader", className); // Check with the primordial class loader if (getParent() != null) { return getParent().loadClass(className); } else { return findSystemClass(className); } } } }