package org.aspectj.apache.bcel.util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.aspectj.apache.bcel.classfile.ClassParser;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.util.ClassLoaderRepository.SoftHashMap.SpecialValue;
public class ClassLoaderRepository implements Repository {
private static java.lang.ClassLoader bootClassLoader = null;
private ClassLoaderReference loaderRef;
private WeakHashMap<URL, SoftReference<JavaClass>> localCache = new WeakHashMap<URL, SoftReference<JavaClass>>();
private static SoftHashMap sharedCache = new SoftHashMap(Collections.synchronizedMap(new HashMap<Object, SpecialValue>()));
private SoftHashMap 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 missSharedEvicted = 0;
private long timeManipulatingURLs = 0L;
private long timeSpentLoading = 0L;
private int classesLoadedCount = 0;
private int misses = 0;
private int cacheHitsLocal = 0;
private int missLocalEvicted = 0;
public ClassLoaderRepository(java.lang.ClassLoader loader) {
this.loaderRef = new DefaultClassLoaderReference((loader != null) ? loader : getBootClassLoader());
}
public ClassLoaderRepository(ClassLoaderReference loaderRef) {
this.loaderRef = loaderRef;
}
private static synchronized java.lang.ClassLoader getBootClassLoader() {
if (bootClassLoader == null) {
bootClassLoader = new URLClassLoader(new URL[0]);
}
return bootClassLoader;
}
public static class SoftHashMap extends AbstractMap {
private Map<Object, SpecialValue> map;
boolean recordMiss = true;
private ReferenceQueue rq = new ReferenceQueue();
public SoftHashMap(Map<Object, SpecialValue> 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);
}
}
@Override
public Object get(Object key) {
SpecialValue value = map.get(key);
if (value == null)
return null;
if (value.get() == null) {
map.remove(value.key);
if (recordMiss)
missSharedEvicted++;
return null;
} else {
return value.get();
}
}
@Override
public Object put(Object k, Object v) {
processQueue();
return map.put(k, new SpecialValue(k, v));
}
@Override
public Set entrySet() {
return map.entrySet();
}
@Override
public void clear() {
processQueue();
map.clear();
}
@Override
public int size() {
processQueue();
return map.size();
}
@Override
public Object remove(Object k) {
processQueue();
SpecialValue value = map.remove(k);
if (value == null)
return null;
if (value.get() != null) {
return value.get();
}
return null;
}
}
private void storeClassAsReference(URL url, JavaClass clazz) {
if (useSharedCache) {
clazz.setRepository(null);
sharedCache.put(url, clazz);
} else {
clazz.setRepository(this);
localCache.put(url, new SoftReference<JavaClass>(clazz));
}
}
public void storeClass(JavaClass clazz) {
storeClassAsReference(toURL(clazz.getClassName()), clazz);
}
public void removeClass(JavaClass clazz) {
if (useSharedCache)
sharedCache.remove(toURL(clazz.getClassName()));
else
localCache.remove(toURL(clazz.getClassName()));
}
public JavaClass findClass(String 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) {
o = ((Reference) o).get();
if (o != null) {
return (JavaClass) o;
} else {
missLocalEvicted++;
}
}
return null;
}
private JavaClass findClassShared(URL url) {
return (JavaClass) sharedCache.get(url);
}
private URL toURL(String className) {
URL url = (URL) nameMap.get(className);
if (url == null) {
String classFile = className.replace('.', '/');
url = loaderRef.getClassLoader().getResource(classFile + ".class");
nameMap.put(className, url);
}
return url;
}
public JavaClass loadClass(String className) throws ClassNotFoundException {
long time = System.currentTimeMillis();
java.net.URL url = toURL(className);
timeManipulatingURLs += (System.currentTimeMillis() - time);
if (url == null)
throw new ClassNotFoundException(className + " not found - unable to determine URL");
JavaClass clazz = null;
if (useSharedCache) {
clazz = findClassShared(url);
if (clazz != null) {
cacheHitsShared++;
return clazz;
}
} else {
clazz = findClassLocal(url);
if (clazz != null) {
cacheHitsLocal++;
return clazz;
}
}
misses++;
try {
String classFile = className.replace('.', '/');
InputStream is = (useSharedCache ? url.openStream() : loaderRef.getClassLoader().getResourceAsStream(
classFile + ".class"));
if (is == null) {
throw new ClassNotFoundException(className + " not found using url " + url);
}
ClassParser parser = new ClassParser(is, className);
clazz = parser.parse();
storeClassAsReference(url, clazz);
timeSpentLoading += (System.currentTimeMillis() - time);
classesLoadedCount++;
return clazz;
} catch (IOException e) {
throw new ClassNotFoundException(e.toString());
}
}
public String report() {
StringBuffer sb = new StringBuffer();
sb.append("BCEL repository report.");
if (useSharedCache)
sb.append(" (shared cache)");
else
sb.append(" (local cache)");
sb.append(" Total time spent loading: " + timeSpentLoading + "ms.");
sb.append(" Time spent manipulating URLs: " + timeManipulatingURLs + "ms.");
sb.append(" Classes loaded: " + classesLoadedCount + ".");
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();
}
public long[] reportStats() {
return new long[] { timeSpentLoading, timeManipulatingURLs, classesLoadedCount, cacheHitsShared, missSharedEvicted,
cacheHitsLocal, missLocalEvicted, sharedCache.size() };
}
public void reset() {
timeManipulatingURLs = 0L;
timeSpentLoading = 0L;
classesLoadedCount = 0;
cacheHitsLocal = 0;
cacheHitsShared = 0;
missSharedEvicted = 0;
missLocalEvicted = 0;
misses = 0;
clear();
}
public JavaClass loadClass(Class clazz) throws ClassNotFoundException {
return loadClass(clazz.getName());
}
public void clear() {
if (useSharedCache)
sharedCache.clear();
else
localCache.clear();
}
}