import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.lang.ref.Reference; | import java.lang.ref.Reference; | ||||
import java.lang.ref.ReferenceQueue; | |||||
import java.lang.ref.SoftReference; | import java.lang.ref.SoftReference; | ||||
import java.net.URL; | import java.net.URL; | ||||
import java.net.URLClassLoader; | import java.net.URLClassLoader; | ||||
import java.util.AbstractMap; | |||||
import java.util.Collections; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import java.util.WeakHashMap; | import java.util.WeakHashMap; | ||||
import org.aspectj.apache.bcel.classfile.ClassParser; | import org.aspectj.apache.bcel.classfile.ClassParser; | ||||
* | * | ||||
* @see org.aspectj.apache.bcel.Repository | * @see org.aspectj.apache.bcel.Repository | ||||
* | * | ||||
* @version $Id: ClassLoaderRepository.java,v 1.7 2006/08/18 14:51:00 acolyer Exp $ | |||||
* @version $Id: ClassLoaderRepository.java,v 1.8 2006/08/21 15:23:58 aclement Exp $ | |||||
* @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A> | * @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A> | ||||
* @author David Dixon-Peugh | * @author David Dixon-Peugh | ||||
*/ | */ | ||||
public class ClassLoaderRepository implements Repository { | public class ClassLoaderRepository implements Repository { | ||||
private static java.lang.ClassLoader bootClassLoader = null; | private static java.lang.ClassLoader bootClassLoader = null; | ||||
private java.lang.ClassLoader loader; | private java.lang.ClassLoader loader; | ||||
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; | |||||
// Choice of cache... | |||||
private WeakHashMap /*<URL,SoftRef(JavaClass)>*/localCache = new WeakHashMap(); | |||||
private static SoftHashMap /*<URL,JavaClass>*/sharedCache = new SoftHashMap(Collections.synchronizedMap(new HashMap())); | |||||
// For fast translation of the classname *intentionally not static* | |||||
private SoftHashMap /*<String,URL>*/ nameMap = new SoftHashMap(new HashMap(), false); | |||||
public static boolean useSharedCache = | |||||
System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true"); | |||||
private static int cacheHitsShared = 0; | private static int cacheHitsShared = 0; | ||||
private static int missSharedEvicted = 0; // Misses in shared cache access due to reference GC | private static int missSharedEvicted = 0; // Misses in shared cache access due to reference GC | ||||
private static int misses = 0; | |||||
private long timeManipulatingURLs = 0L; | |||||
private long timeSpentLoading = 0L; | |||||
private int classesLoadedCount = 0; | |||||
private int misses = 0; | |||||
private int cacheHitsLocal = 0; | private int cacheHitsLocal = 0; | ||||
private int missLocalEvicted = 0; // Misses in local cache access due to reference GC | 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 ) { | public ClassLoaderRepository( java.lang.ClassLoader loader ) { | ||||
this.loader = (loader != null) ? loader : getBootClassLoader(); | |||||
this.loader = (loader != null) ? loader : getBootClassLoader(); | |||||
} | } | ||||
private static synchronized java.lang.ClassLoader getBootClassLoader() { | private static synchronized java.lang.ClassLoader getBootClassLoader() { | ||||
} | } | ||||
return bootClassLoader; | return bootClassLoader; | ||||
} | } | ||||
// Can track back to its key | |||||
public static class SoftHashMap extends AbstractMap { | |||||
private Map map; | |||||
boolean recordMiss = true; // only interested in recording miss stats sometimes | |||||
private ReferenceQueue rq = new ReferenceQueue(); | |||||
public SoftHashMap(Map map) { this.map = map; } | |||||
public SoftHashMap() { this(new HashMap()); } | |||||
public SoftHashMap(Map map, boolean b) { this(map); this.recordMiss=b;} | |||||
class SpecialValue extends SoftReference { | |||||
private final Object key; | |||||
SpecialValue(Object k,Object v) { | |||||
super(v,rq); | |||||
this.key = k; | |||||
} | |||||
} | |||||
private void processQueue() { | |||||
SpecialValue sv = null; | |||||
while ((sv = (SpecialValue)rq.poll())!=null) { | |||||
map.remove(sv.key); | |||||
} | |||||
} | |||||
public Object get(Object key) { | |||||
SpecialValue value = (SpecialValue)map.get(key); | |||||
if (value==null) return null; | |||||
if (value.get()==null) { | |||||
// it got GC'd | |||||
map.remove(value.key); | |||||
if (recordMiss) missSharedEvicted++; | |||||
return null; | |||||
} else { | |||||
return value.get(); | |||||
} | |||||
} | |||||
public Object put(Object k, Object v) { | |||||
processQueue(); | |||||
return map.put(k, new SpecialValue(k,v)); | |||||
} | |||||
public Set entrySet() { | |||||
return map.entrySet(); | |||||
} | |||||
public void clear() { | |||||
processQueue(); | |||||
map.clear(); | |||||
} | |||||
public int size() { | |||||
processQueue(); | |||||
return map.size(); | |||||
} | |||||
public Object remove(Object k) { | |||||
processQueue(); | |||||
SpecialValue value = (SpecialValue)map.remove(k); | |||||
if (value==null) return null; | |||||
if (value.get()!=null) { | |||||
return value.get(); | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
/** | /** | ||||
* Store a new JavaClass into this repository as a soft reference and return the reference | * 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; | |||||
private void storeClassAsReference(URL url, JavaClass clazz ) { | |||||
if (useSharedCache) { | |||||
clazz.setRepository(null); // can't risk setting repository, we'll get in a pickle! | |||||
sharedCache.put(url, clazz); | |||||
} else { | |||||
clazz.setRepository(this); | |||||
localCache.put(url, new SoftReference(clazz)); | |||||
} | |||||
} | } | ||||
/** | |||||
* 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. | * Store a new JavaClass into this Repository. | ||||
*/ | */ | ||||
public void storeClass( JavaClass clazz ) { | public void storeClass( JavaClass clazz ) { | ||||
storeClassAsReference(clazz); | |||||
storeClassAsReference(toURL(clazz.getClassName()),clazz); | |||||
} | } | ||||
/** | /** | ||||
* Remove class from repository | * Remove class from repository | ||||
*/ | */ | ||||
public void removeClass(JavaClass clazz) { | public void removeClass(JavaClass clazz) { | ||||
loadedClassesLocalCache.remove(clazz.getClassName()); | |||||
if (useSharedCache) sharedCache.remove(toURL(clazz.getClassName())); | |||||
else localCache.remove(toURL(clazz.getClassName())); | |||||
} | } | ||||
/** | /** | ||||
* Find an already defined JavaClass in the local cache. | * Find an already defined JavaClass in the local cache. | ||||
*/ | */ | ||||
public JavaClass findClass( String className ) { | public JavaClass findClass( String className ) { | ||||
Object o = loadedClassesLocalCache.get( className ); | |||||
if (useSharedCache) return findClassShared(toURL(className)); | |||||
else return findClassLocal(toURL(className)); | |||||
} | |||||
private JavaClass findClassLocal( URL url ) { | |||||
Object o = localCache.get(url); | |||||
if (o != null) { | if (o != null) { | ||||
o = ((Reference)o).get(); | o = ((Reference)o).get(); | ||||
if (o != null) { | if (o != null) { | ||||
* Find an already defined JavaClass in the shared cache. | * Find an already defined JavaClass in the shared cache. | ||||
*/ | */ | ||||
private JavaClass findClassShared(URL url) { | 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; | |||||
return (JavaClass)sharedCache.get(url); | |||||
} | } | ||||
private URL toURL(String className) { | |||||
URL url = (URL)nameMap.get(className); | |||||
if (url==null) { | |||||
String classFile = className.replace('.', '/'); | |||||
url = loader.getResource( classFile + ".class" ); | |||||
nameMap.put(className, url); | |||||
} | |||||
return url; | |||||
} | |||||
/** | /** | ||||
* Lookup a JavaClass object from the Class Name provided. | * 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('.', '/'); | |||||
// Check the local cache | |||||
JavaClass clazz = findClass(className); | |||||
if (clazz != null) { cacheHitsLocal++; return clazz; } | |||||
// translate to a URL | |||||
long time = System.currentTimeMillis(); | |||||
java.net.URL url = toURL(className); | |||||
timeManipulatingURLs += (System.currentTimeMillis() - time); | |||||
if (url==null) throw new ClassNotFoundException(className + " not found."); | |||||
JavaClass clazz = null; | |||||
try { | |||||
// 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 | |||||
// Look in the appropriate cache | |||||
if (useSharedCache) { | |||||
clazz = findClassShared(url); | clazz = findClassShared(url); | ||||
if (clazz != null) { cacheHitsShared++; timeSpentLoading+=(System.currentTimeMillis() - time); return clazz; } | |||||
if (clazz != null) { cacheHitsShared++; return clazz; } | |||||
} else { | |||||
clazz = findClassLocal(url); | |||||
if (clazz != null) { cacheHitsLocal++; return clazz; } | |||||
} | |||||
// Didn't find it in either cache | |||||
misses++; | |||||
// Didn't find it in either cache | |||||
misses++; | |||||
if (is == null) { // TODO move this up? | |||||
throw new ClassNotFoundException(className + " not found."); | |||||
} | |||||
ClassParser parser = new ClassParser( is, className ); | |||||
try { | |||||
// Load it | |||||
String classFile = className.replace('.', '/'); | |||||
InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" )); | |||||
if (is == null) { | |||||
throw new ClassNotFoundException(className + " not found."); | |||||
} | |||||
ClassParser parser = new ClassParser( is, className ); | |||||
clazz = parser.parse(); | clazz = parser.parse(); | ||||
// Store it in both caches | |||||
Reference ref = storeClassAsReference( clazz ); | |||||
storeReferenceShared(url,ref); | |||||
// Cache it | |||||
storeClassAsReference(url, clazz ); | |||||
timeSpentLoading += (System.currentTimeMillis() - time); | timeSpentLoading += (System.currentTimeMillis() - time); | ||||
classesLoadedCount++; | classesLoadedCount++; | ||||
} | } | ||||
/** | |||||
/** | |||||
* Produce a report on cache usage. | * Produce a report on cache usage. | ||||
*/ | */ | ||||
public String reportAllStatistics() { | |||||
public String report() { | |||||
StringBuffer sb = new StringBuffer(); | StringBuffer sb = new StringBuffer(); | ||||
sb.append("BCEL repository report."); | sb.append("BCEL repository report."); | ||||
if (!useSharedCache) sb.append(" (Shared cache deactivated)"); | |||||
if (useSharedCache) sb.append(" (shared cache)"); | |||||
else sb.append(" (local cache)"); | |||||
sb.append(" Total time spent loading: "+timeSpentLoading+"ms."); | sb.append(" Total time spent loading: "+timeSpentLoading+"ms."); | ||||
sb.append(" Time manipulating URLs: "+timeManipulatingURLs+"ms."); | |||||
sb.append(" Time spent manipulating URLs: "+timeManipulatingURLs+"ms."); | |||||
sb.append(" Classes loaded: "+classesLoadedCount+"."); | sb.append(" Classes loaded: "+classesLoadedCount+"."); | ||||
if (useSharedCache) sb.append(" URL cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+")."); | |||||
sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+")."); | |||||
if (useSharedCache) { | |||||
sb.append(" Shared cache size: "+sharedCache.size()); | |||||
sb.append(" Shared cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+")."); | |||||
} else { | |||||
sb.append(" Local cache size: "+localCache.size()); | |||||
sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+")."); | |||||
} | |||||
return sb.toString(); | return sb.toString(); | ||||
} | } | ||||
public int reportLocalCacheHits() { | |||||
return cacheHitsLocal; | |||||
} | |||||
public static int reportSharedCacheHits() { | |||||
return cacheHitsShared; | |||||
/** | |||||
* Returns an array of the stats, for testing, the order is fixed: | |||||
* 0=time spent loading (static) | |||||
* 1=time spent manipulating URLs (static) | |||||
* 2=classes loaded (static) | |||||
* 3=cache hits shared (static) | |||||
* 4=misses in shared due to eviction (static) | |||||
* 5=cache hits local | |||||
* 6=misses in local due to eviction | |||||
* 7=shared cache size | |||||
*/ | |||||
public long[] reportStats() { | |||||
return new long[]{timeSpentLoading,timeManipulatingURLs,classesLoadedCount, | |||||
cacheHitsShared,missSharedEvicted,cacheHitsLocal,missLocalEvicted, | |||||
sharedCache.size()}; | |||||
} | } | ||||
/** | /** | ||||
missLocalEvicted = 0; | missLocalEvicted = 0; | ||||
misses = 0; | misses = 0; | ||||
clear(); | clear(); | ||||
clearShared(); | |||||
} | } | ||||
/** Clear all entries from the local cache */ | /** Clear all entries from the local cache */ | ||||
public void clear() { | public void clear() { | ||||
loadedClassesLocalCache.clear(); | |||||
} | |||||
/** Clear all entries from the shared cache */ | |||||
public static void clearShared() { | |||||
loadedUrlsSharedCache.clear(); | |||||
if (useSharedCache) sharedCache.clear(); | |||||
else localCache.clear(); | |||||
} | } | ||||
} | } | ||||
// Retrieve string 5 times from same repository, 4 hits should be from local cache | // Retrieve string 5 times from same repository, 4 hits should be from local cache | ||||
public void testLocalCacheWorks() throws ClassNotFoundException { | public void testLocalCacheWorks() throws ClassNotFoundException { | ||||
ClassLoaderRepository.useSharedCache=false; | |||||
JavaClass jc = rep1.loadClass("java.lang.String"); | 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"); | ||||
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); | |||||
assertTrue("Should have used local cache 4 times: "+reportLocalCacheHits(rep1),reportLocalCacheHits(rep1)==4); | |||||
} | |||||
// Retrieve string 5 times from same repository, 4 hits should be from local cache | |||||
public void testSharedCacheWorksOnOne() throws ClassNotFoundException { | |||||
ClassLoaderRepository.useSharedCache=true; | |||||
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: "+reportSharedCacheHits(rep1),reportSharedCacheHits(rep1)==4); | |||||
} | } | ||||
// Retrieve String through one repository then load again through another, should be shared cache hit | // Retrieve String through one repository then load again through another, should be shared cache hit | ||||
public void testSharedCacheWorks() throws ClassNotFoundException { | public void testSharedCacheWorks() throws ClassNotFoundException { | ||||
ClassLoaderRepository.useSharedCache=true; | |||||
JavaClass jc = rep1.loadClass("java.lang.String"); | JavaClass jc = rep1.loadClass("java.lang.String"); | ||||
jc = rep2.loadClass("java.lang.String"); | jc = rep2.loadClass("java.lang.String"); | ||||
assertTrue("Should have retrieved String from shared cache: "+ClassLoaderRepository.reportSharedCacheHits(), | |||||
ClassLoaderRepository.reportSharedCacheHits()==1); | |||||
assertTrue("Should have retrieved String from shared cache: "+reportSharedCacheHits(rep1), | |||||
reportSharedCacheHits(rep1)==1); | |||||
} | } | ||||
// Shared cache OFF, shouldn't get a shared cache hit | // Shared cache OFF, shouldn't get a shared cache hit | ||||
JavaClass jc = rep1.loadClass("java.lang.String"); | JavaClass jc = rep1.loadClass("java.lang.String"); | ||||
jc = rep2.loadClass("java.lang.String"); | jc = rep2.loadClass("java.lang.String"); | ||||
assertTrue("Should not have retrieved String from shared cache: "+ | assertTrue("Should not have retrieved String from shared cache: "+ | ||||
ClassLoaderRepository.reportSharedCacheHits(), | |||||
ClassLoaderRepository.reportSharedCacheHits()==0); | |||||
reportSharedCacheHits(rep1), | |||||
reportSharedCacheHits(rep1)==0); | |||||
} finally { | } finally { | ||||
ClassLoaderRepository.useSharedCache=true; | ClassLoaderRepository.useSharedCache=true; | ||||
} | } | ||||
public void tearDown() throws Exception { | public void tearDown() throws Exception { | ||||
super.tearDown(); | super.tearDown(); | ||||
System.err.println("Rep1: "+rep1.reportAllStatistics()); | |||||
System.err.println("Rep2: "+rep2.reportAllStatistics()); | |||||
System.err.println("Rep1: "+rep1.reportStats()); | |||||
System.err.println("Rep2: "+rep2.reportStats()); | |||||
rep1.reset(); | rep1.reset(); | ||||
rep2.reset(); | rep2.reset(); | ||||
} | } | ||||
private long reportLocalCacheHits(ClassLoaderRepository rep) { | |||||
return rep.reportStats()[5]; | |||||
} | |||||
private long reportSharedCacheHits(ClassLoaderRepository rep) { | |||||
return rep.reportStats()[3]; | |||||
} | |||||
} | } | ||||
WeaverStateInfo wsi = type.getWeaverState(); | WeaverStateInfo wsi = type.getWeaverState(); | ||||
if (wsi != null && wsi.isReweavable()) { | if (wsi != null && wsi.isReweavable()) { | ||||
BcelObjectType classType = getClassType(type.getName()); | BcelObjectType classType = getClassType(type.getName()); | ||||
classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes()))); | |||||
JavaClass wovenJavaClass = classType.getJavaClass(); | |||||
JavaClass unwovenJavaClass = Utility.makeJavaClass(wovenJavaClass.getFileName(), | |||||
wsi.getUnwovenClassFileData(wovenJavaClass.getBytes())); | |||||
world.storeClass(unwovenJavaClass); | |||||
classType.setJavaClass(unwovenJavaClass); | |||||
// classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes()))); | |||||
} | } | ||||
//TODO AV - happens to reach that a lot of time: for each type flagged reweavable X for each aspect in the weaverstate | //TODO AV - happens to reach that a lot of time: for each type flagged reweavable X for each aspect in the weaverstate |
public class BcelWorld extends World implements Repository { | public class BcelWorld extends World implements Repository { | ||||
private ClassPathManager classPath; | private ClassPathManager classPath; | ||||
private Repository delegate; | |||||
protected Repository delegate; | |||||
//private ClassPathManager aspectPath = null; | //private ClassPathManager aspectPath = null; | ||||
} | } | ||||
public void storeClass(JavaClass clazz) { | public void storeClass(JavaClass clazz) { | ||||
throw new RuntimeException("Not implemented"); | |||||
// doesn't need to do anything | |||||
} | } | ||||
public void removeClass(JavaClass clazz) { | public void removeClass(JavaClass clazz) { |
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import org.aspectj.apache.bcel.classfile.JavaClass; | |||||
import org.aspectj.bridge.IMessageHandler; | import org.aspectj.bridge.IMessageHandler; | ||||
import org.aspectj.util.LangUtil; | import org.aspectj.util.LangUtil; | ||||
import org.aspectj.weaver.ICrossReferenceHandler; | import org.aspectj.weaver.ICrossReferenceHandler; | ||||
*/ | */ | ||||
public class LTWWorld extends BcelWorld implements IReflectionWorld { | public class LTWWorld extends BcelWorld implements IReflectionWorld { | ||||
private AnnotationFinder annotationFinder; | private AnnotationFinder annotationFinder; | ||||
private ClassLoader loader; // weavingContext? | private ClassLoader loader; // weavingContext? | ||||
private IWeavingContext weavingContext; | private IWeavingContext weavingContext; | ||||
((ReferenceType)ret).setDelegate(rtd); | ((ReferenceType)ret).setDelegate(rtd); | ||||
return ret; | return ret; | ||||
} | } | ||||
public void storeClass(JavaClass clazz) { | |||||
delegate.storeClass(clazz); | |||||
} | |||||
} | } |
package org.aspectj.weaver.bcel; | package org.aspectj.weaver.bcel; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | |||||
import java.lang.ref.Reference; | |||||
import java.lang.reflect.Field; | |||||
import java.net.URL; | import java.net.URL; | ||||
import java.net.URLClassLoader; | import java.net.URLClassLoader; | ||||
import java.util.Enumeration; | import java.util.Enumeration; | ||||
import java.util.Map; | |||||
import java.util.zip.ZipEntry; | import java.util.zip.ZipEntry; | ||||
import java.util.zip.ZipFile; | import java.util.zip.ZipFile; | ||||
/** NOT YET INCLUDED IN A FULL TEST RUN - WORK IN PROGRESS CHECKING CLASSLOADERREPOSITORY OPTIMIZATIONS */ | /** NOT YET INCLUDED IN A FULL TEST RUN - WORK IN PROGRESS CHECKING CLASSLOADERREPOSITORY OPTIMIZATIONS */ | ||||
public class ClassLoaderRepositoryTests extends TestCase { | public class ClassLoaderRepositoryTests extends TestCase { | ||||
public void testRepositorySharing() throws Exception { | |||||
ClassLoaderRepository.useSharedCache=false; | |||||
File f = new File("../lib/aspectj/lib/aspectjtools.jar"); | |||||
private File f; | |||||
private ZipFile zf; | |||||
private Enumeration entries; | |||||
private Map map; | |||||
public void setUp() throws Exception { | |||||
f = new File("../lib/aspectj/lib/aspectjtools.jar"); | |||||
assertTrue("Couldn't find aspectjtools to test. Tried: "+f.getAbsolutePath(),f.exists()); | |||||
zf = new ZipFile(f); | |||||
entries = zf.entries(); | |||||
// ClassLoaderRepository.sharedCacheCompactFrequency = 16384; | |||||
map = getSharedMap(); | |||||
} | |||||
public void tearDown() { | |||||
new ClassLoaderRepository(null).reset(); | |||||
} | |||||
private ClassLoaderRepository setupRepository() throws Exception { | |||||
ClassLoader cl = Thread.currentThread().getContextClassLoader(); | ClassLoader cl = Thread.currentThread().getContextClassLoader(); | ||||
ClassLoader cl1 = new URLClassLoader(new URL[]{f.toURL()},cl); | |||||
ClassLoader cl2 = new URLClassLoader(new URL[]{f.toURL()},cl); | |||||
ClassLoaderRepository rep1 = new ClassLoaderRepository(cl1); | |||||
ClassLoaderRepository rep2 = new ClassLoaderRepository(cl2); | |||||
try { | |||||
assertTrue("Couldnt find aspectjtools to test. Tried: "+f.getAbsolutePath(),f.exists()); | |||||
ZipFile zf = new ZipFile(f); | |||||
int i = 0; | |||||
Enumeration entries = zf.entries(); | |||||
while (entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry)entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); | |||||
// twice by each | |||||
rep1.loadClass(clazzname); | |||||
rep1.loadClass(clazzname); | |||||
rep2.loadClass(clazzname); | |||||
rep2.loadClass(clazzname); | |||||
i++; | |||||
} | |||||
ClassLoader res = new URLClassLoader(new URL[]{f.toURL()},cl); | |||||
ClassLoaderRepository rep = new ClassLoaderRepository(res); | |||||
return rep; | |||||
} | |||||
private void compareTwoRepositories() throws Exception { | |||||
ClassLoaderRepository rep1 = setupRepository(); | |||||
ClassLoaderRepository rep2 = setupRepository(); | |||||
int i = 0; | |||||
while (entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry)entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); | |||||
// twice by each | |||||
rep1.loadClass(clazzname); | |||||
rep1.loadClass(clazzname); | |||||
rep2.loadClass(clazzname); | |||||
rep2.loadClass(clazzname); | |||||
i++; | |||||
} | |||||
} | |||||
System.err.println("Successfully compared "+i+" entries!!"); | |||||
System.err.println(rep1.report()); | |||||
System.err.println(rep2.report()); | |||||
} | |||||
private void loadOnce() throws Exception { | |||||
ClassLoaderRepository rep = setupRepository(); | |||||
while (entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry) entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0, | |||||
classfileName.length() - 6).replace('/', '.'); | |||||
rep.loadClass(clazzname); | |||||
} | |||||
} | |||||
} | |||||
public void testMultiThreaded() throws Throwable { | |||||
ClassLoaderRepository.useSharedCache=true; | |||||
// ClassLoaderRepository.sharedCacheCompactFrequency = 200; | |||||
//loadOnce(); | |||||
TestThread threads[] = new TestThread[6]; | |||||
for (int i=0; i<threads.length; i++) { | |||||
threads[i] = new TestThread((i%3)*1000); | |||||
threads[i].start(); | |||||
} | |||||
for (int i=0; i<threads.length; i++) { | |||||
threads[i].join(); | |||||
if (threads[i].getFailure() != null) { | |||||
throw threads[i].getFailure(); | |||||
} | |||||
} | |||||
} | |||||
private class TestThread extends Thread { | |||||
public Throwable failure = null; | |||||
Enumeration entries; | |||||
// ensure the threads are loading DIFFERENT shared classes at the same time... | |||||
public TestThread(int skip) { | |||||
entries = zf.entries(); | |||||
for (int i=0; i<skip && entries.hasMoreElements(); i++) { | |||||
entries.nextElement(); | |||||
} | } | ||||
System.err.println("Successfully compared "+i+" entries!!"); | |||||
} catch (IOException e) { | |||||
e.printStackTrace(); | |||||
fail(e.getMessage()); | |||||
} | |||||
public void run() { | |||||
try { | |||||
ClassLoaderRepository rep = setupRepository(); | |||||
int i = 0; | |||||
while (entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry)entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); | |||||
rep.loadClass(clazzname); | |||||
rep.loadClass(clazzname); | |||||
i++; | |||||
} | |||||
} | |||||
System.err.println("Thread finished: "+rep.report()); | |||||
} catch (Throwable t) { | |||||
failure = t; | |||||
} | |||||
} | |||||
public Throwable getFailure() { | |||||
return failure; | |||||
} | } | ||||
System.err.println(rep1.reportAllStatistics()); | |||||
System.err.println(rep2.reportAllStatistics()); | |||||
} | |||||
public void testNotSharedRepository() throws Exception { | |||||
ClassLoaderRepository.useSharedCache=false; | |||||
compareTwoRepositories(); | |||||
} | |||||
public void testSharedUrlRepository() throws Exception { | |||||
ClassLoaderRepository.useSharedCache=true; | |||||
compareTwoRepositories(); | |||||
// ClassLoaderRepository.compactSharedCache(); | |||||
} | } | ||||
public void testPurgeUrlRepository() throws Exception { | |||||
ClassLoaderRepository.useSharedCache = true; | |||||
ClassLoaderRepository rep = setupRepository(); | |||||
Reference ref = null; | |||||
while (ref==null && entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry)entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); | |||||
rep.loadClass(clazzname); | |||||
assertEquals("expected one entry in shared URL cache "+map.size(), 1, map.size()); | |||||
ref = (Reference)map.values().iterator().next(); | |||||
ref.clear(); | |||||
ref.enqueue(); | |||||
map.size();//force purge | |||||
} | |||||
} | |||||
assertEquals("expected empty shared URL cache "+map.size(), 0, map.size()); | |||||
} | |||||
public void testAutoPurgeUrlRepository() throws Exception { | |||||
ClassLoaderRepository.useSharedCache = true; | |||||
assertEquals("expected empty shared URL cache "+map.size(), 0, map.size()); | |||||
ClassLoaderRepository rep = setupRepository(); | |||||
Reference ref = null; | |||||
int i = 0; | |||||
while (i<3 && entries.hasMoreElements()) { | |||||
ZipEntry zfe = (ZipEntry)entries.nextElement(); | |||||
String classfileName = zfe.getName(); | |||||
if (classfileName.endsWith(".class")) { | |||||
String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); | |||||
rep.loadClass(clazzname); | |||||
ref = (Reference)map.values().iterator().next(); | |||||
ref.clear(); | |||||
ref.enqueue(); | |||||
i++; | |||||
} | |||||
} | |||||
assertTrue("expected smaller shared URL cache "+map.size(), map.size()<3); | |||||
} | |||||
private Field getSharedMapField() throws Exception { | |||||
Field field = ClassLoaderRepository.class.getDeclaredField("sharedCache"); | |||||
field.setAccessible(true); | |||||
return field; | |||||
} | |||||
private Map getSharedMap() throws Exception { | |||||
return (Map)getSharedMapField() .get(null); | |||||
} | |||||
} | } | ||||