import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
import java.util.WeakHashMap;
import org.aspectj.apache.bcel.classfile.ClassParser;
*
* @see org.aspectj.apache.bcel.Repository
*
- * @version $Id: ClassLoaderRepository.java,v 1.5 2006/03/10 13:29:05 aclement Exp $
+ * @version $Id: ClassLoaderRepository.java,v 1.6 2006/08/08 11:26:28 aclement Exp $
* @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
* @author David Dixon-Peugh
*/
-public class ClassLoaderRepository
- implements Repository
-{
+public class ClassLoaderRepository implements Repository {
private java.lang.ClassLoader loader;
- private WeakHashMap loadedClasses =
- new WeakHashMap(); // CLASSNAME X JAVACLASS
+ private WeakHashMap /*<String classname,JavaClass>*/loadedClassesLocalCache = new WeakHashMap();
+ private static Map /*<URL,JavaClass>*/loadedUrlsSharedCache = new HashMap();
+ public static boolean useSharedCache = true;
+
+ private static long timeManipulatingURLs = 0L;
+ private static long timeSpentLoading = 0L;
+ private static int classesLoadedCount = 0;
+ private static int cacheHitsShared = 0;
+ private static int missSharedEvicted = 0; // Misses in shared cache access due to reference GC
+ private static int misses = 0;
+ private int cacheHitsLocal = 0;
+ private int missLocalEvicted = 0; // Misses in local cache access due to reference GC
+ static {
+ useSharedCache = System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true");
+ }
+
public ClassLoaderRepository( java.lang.ClassLoader loader ) {
this.loader = loader;
}
+ /**
+ * Store a new JavaClass into this repository as a soft reference and return the reference
+ */
+ private Reference storeClassAsReference( JavaClass clazz ) {
+ Reference ref = new SoftReference(clazz);
+ loadedClassesLocalCache.put( clazz.getClassName(), ref);
+ clazz.setRepository( this );
+ return ref;
+ }
+
+ /**
+ * Store a reference in the shared cache
+ */
+ private void storeReferenceShared(URL url, Reference ref) {
+ if (useSharedCache) loadedUrlsSharedCache.put(url, ref);
+ }
+
/**
* Store a new JavaClass into this Repository.
*/
public void storeClass( JavaClass clazz ) {
- loadedClasses.put( clazz.getClassName(),
- clazz );
- clazz.setRepository( this );
+ storeClassAsReference(clazz);
}
/**
* Remove class from repository
*/
public void removeClass(JavaClass clazz) {
- loadedClasses.remove(clazz.getClassName());
+ loadedClassesLocalCache.remove(clazz.getClassName());
}
/**
- * Find an already defined JavaClass.
+ * Find an already defined JavaClass in the local cache.
*/
public JavaClass findClass( String className ) {
- if ( loadedClasses.containsKey( className )) {
- return (JavaClass) loadedClasses.get( className );
- } else {
- return null;
+ Object o = loadedClassesLocalCache.get( className );
+ if (o != null) {
+ o = ((Reference)o).get();
+ if (o != null) {
+ return (JavaClass)o;
+ } else {
+ missLocalEvicted++;
+ }
}
+ return null;
+ }
+
+ /**
+ * Find an already defined JavaClass in the shared cache.
+ */
+ private JavaClass findClassShared(URL url) {
+ if (!useSharedCache) return null;
+ Object o = (Reference)loadedUrlsSharedCache.get(url);
+ if (o != null) {
+ o = ((Reference)o).get();
+ if (o != null) {
+ return (JavaClass)o;
+ } else {
+ missSharedEvicted++;
+ }
+ }
+ return null;
}
+
/**
* Lookup a JavaClass object from the Class Name provided.
*/
- public JavaClass loadClass( String className )
- throws ClassNotFoundException
- {
+ public JavaClass loadClass( String className ) throws ClassNotFoundException {
String classFile = className.replace('.', '/');
- JavaClass RC = findClass( className );
- if (RC != null) { return RC; }
+ // Check the local cache
+ JavaClass clazz = findClass(className);
+ if (clazz != null) { cacheHitsLocal++; return clazz; }
try {
- InputStream is =
- loader.getResourceAsStream( classFile + ".class" );
+ // Work out the URL
+ long time = System.currentTimeMillis();
+ java.net.URL url = (useSharedCache?loader.getResource( classFile + ".class" ):null);
+ if (useSharedCache && url==null) throw new ClassNotFoundException(className + " not found.");
+ InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" ));
+ timeManipulatingURLs += (System.currentTimeMillis() - time);
+
+ // Check the shared cache
+ clazz = findClassShared(url);
+ if (clazz != null) { cacheHitsShared++; timeSpentLoading+=(System.currentTimeMillis() - time); return clazz; }
+
+ // Didn't find it in either cache
+ misses++;
- if(is == null) {
- throw new ClassNotFoundException(className + " not found.");
- }
+ if (is == null) { // TODO move this up?
+ throw new ClassNotFoundException(className + " not found.");
+ }
- ClassParser parser = new ClassParser( is, className );
- RC = parser.parse();
+ ClassParser parser = new ClassParser( is, className );
+ clazz = parser.parse();
- storeClass( RC );
+ // Store it in both caches
+ Reference ref = storeClassAsReference( clazz );
+ storeReferenceShared(url,ref);
- return RC;
+ timeSpentLoading += (System.currentTimeMillis() - time);
+ classesLoadedCount++;
+ return clazz;
} catch (IOException e) {
throw new ClassNotFoundException( e.toString() );
}
}
+
+/**
+ * Produce a report on cache usage.
+ */
+ public String reportAllStatistics() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("BCEL repository report.");
+ if (!useSharedCache) sb.append(" (Shared cache deactivated)");
+ sb.append(" Total time spent loading: "+timeSpentLoading+"ms.");
+ sb.append(" Time manipulating URLs: "+timeManipulatingURLs+"ms.");
+ sb.append(" Classes loaded: "+classesLoadedCount+".");
+ if (useSharedCache) sb.append(" URL cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+").");
+ sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+").");
+ return sb.toString();
+ }
+
+ public int reportLocalCacheHits() {
+ return cacheHitsLocal;
+ }
+
+ public static int reportSharedCacheHits() {
+ return cacheHitsShared;
+ }
+
+ /**
+ * Reset statistics and clear all caches
+ */
+ public void reset() {
+ timeManipulatingURLs = 0L;
+ timeSpentLoading = 0L;
+ classesLoadedCount = 0;
+ cacheHitsLocal = 0;
+ cacheHitsShared = 0;
+ missSharedEvicted = 0;
+ missLocalEvicted = 0;
+ misses = 0;
+ clear();
+ clearShared();
+ }
+
+
public JavaClass loadClass(Class clazz) throws ClassNotFoundException {
return loadClass(clazz.getName());
}
- /** Clear all entries from cache.
- */
+ /** Clear all entries from the local cache */
public void clear() {
- loadedClasses.clear();
+ loadedClassesLocalCache.clear();
+ }
+
+ /** Clear all entries from the shared cache */
+ public static void clearShared() {
+ loadedUrlsSharedCache.clear();
}
}
--- /dev/null
+package org.aspectj.apache.bcel.classfile.tests;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import org.aspectj.apache.bcel.classfile.JavaClass;
+import org.aspectj.apache.bcel.util.ClassLoaderRepository;
+
+import junit.framework.TestCase;
+
+/*
+ * Tests create a simple classloader repository configuration and check sharing of information.
+ */
+public class ClassloaderRepositoryTest extends TestCase {
+
+ private ClassLoaderRepository rep1,rep2;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ ClassLoader cl1 = new URLClassLoader(new URL[]{},cl);
+ ClassLoader cl2 = new URLClassLoader(new URL[]{},cl);
+ rep1 = new ClassLoaderRepository(cl1);
+ rep2 = new ClassLoaderRepository(cl2);
+ }
+
+ // Retrieve string 5 times from same repository, 4 hits should be from local cache
+ public void testLocalCacheWorks() throws ClassNotFoundException {
+ JavaClass jc = rep1.loadClass("java.lang.String");
+ jc = rep1.loadClass("java.lang.String");
+ jc = rep1.loadClass("java.lang.String");
+ jc = rep1.loadClass("java.lang.String");
+ jc = rep1.loadClass("java.lang.String");
+ assertTrue("Should have used local cache 4 times: "+rep1.reportLocalCacheHits(),rep1.reportLocalCacheHits()==4);
+ }
+
+ // Retrieve String through one repository then load again through another, should be shared cache hit
+ public void testSharedCacheWorks() throws ClassNotFoundException {
+ JavaClass jc = rep1.loadClass("java.lang.String");
+ jc = rep2.loadClass("java.lang.String");
+ assertTrue("Should have retrieved String from shared cache: "+ClassLoaderRepository.reportSharedCacheHits(),
+ ClassLoaderRepository.reportSharedCacheHits()==1);
+ }
+
+ // Shared cache OFF, shouldn't get a shared cache hit
+ public void testSharedCacheCanBeDeactivated() throws ClassNotFoundException {
+ try {
+ ClassLoaderRepository.useSharedCache=false;
+ JavaClass jc = rep1.loadClass("java.lang.String");
+ jc = rep2.loadClass("java.lang.String");
+ assertTrue("Should not have retrieved String from shared cache: "+
+ ClassLoaderRepository.reportSharedCacheHits(),
+ ClassLoaderRepository.reportSharedCacheHits()==0);
+ } finally {
+ ClassLoaderRepository.useSharedCache=true;
+ }
+ }
+
+ public void tearDown() throws Exception {
+ super.tearDown();
+ System.err.println("Rep1: "+rep1.reportAllStatistics());
+ System.err.println("Rep2: "+rep2.reportAllStatistics());
+ rep1.reset();
+ rep2.reset();
+ }
+
+}