package kilim.mirrors;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* CachedClassMirrors caches information about a set of classes that are loaded through byte arrays, and which
* are not already loaded by the classloader
**/
public class CachedClassMirrors implements Mirrors {
final static String[] EMPTY_SET = new String[0];
final RuntimeClassMirrors delegate;
ConcurrentHashMap<String,ClassMirror> cachedClasses = new ConcurrentHashMap<String, ClassMirror>();
public CachedClassMirrors(ClassLoader cl) {
delegate = new RuntimeClassMirrors(cl);
}
@Override
public ClassMirror classForName(String className)
throws ClassMirrorNotFoundException {
// defer to loaded class objects first, then to cached class mirrors.
ClassMirror ret = cachedClasses.get(className);
if (ret == null) {
ret = delegate.classForName(className);
}
if (ret == null) {
throw new ClassMirrorNotFoundException(className);
}
return ret;
}
@Override
public ClassMirror mirror(Class<?> clazz) {
// param is already a class; use the delegate to get the appropriate runtime mirror
return delegate.mirror(clazz);
}
@Override
public ClassMirror mirror(String className, byte[] bytecode) {
// if it is loaded by the classLoader already, we will
// not load the classNode, even if the bytes are different
ClassMirror ret = null;
if (!delegate.isLoaded(className)) {
ret = new CachedClassMirror(bytecode);
String name = ret.getName().replace('/', '.'); // Class.forName format
this.cachedClasses.put(name, ret);
}
return ret;
}
}
class CachedClassMirror extends ClassVisitor implements ClassMirror {
String name;
boolean isInterface;
MethodMirror[] declaredMethods;
String[] interfaceNames;
String superName;
private List<CachedMethodMirror> tmpMethodList; //used only while processing bytecode.
public CachedClassMirror(byte []bytecode) {
super(Opcodes.ASM4);
ClassReader cr = new ClassReader(bytecode);
cr.accept(this, /*flags*/0);
}
@Override
public String getName() {
return name;
}
@Override
public boolean isInterface() {
return isInterface;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CachedClassMirror) {
CachedClassMirror mirr = (CachedClassMirror) obj;
return mirr.name == this.name && mirr.isInterface == this.isInterface;
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public MethodMirror[] getDeclaredMethods() {
return (declaredMethods == null) ?
new MethodMirror[0] : declaredMethods;
}
@Override
public String[] getInterfaces() throws ClassMirrorNotFoundException {
return interfaceNames;
}
@Override
public String getSuperclass() throws ClassMirrorNotFoundException {
return superName;
}
@Override
public boolean isAssignableFrom(ClassMirror c) throws ClassMirrorNotFoundException {
Detector d = Detector.getDetector();
if (this.equals(c)) return true;
ClassMirror supcl = d.classForName(c.getSuperclass());
if (isAssignableFrom(supcl)) return true;
for (String icl: c.getInterfaces()) {
supcl = d.classForName(icl);
if (isAssignableFrom(supcl))
return true;
}
return false;
}
// ClassVisitor implementation
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
this.name = name;
this.superName = superName;
this.interfaceNames = interfaces == null ? CachedClassMirrors.EMPTY_SET : interfaces;
this.isInterface = (access & Opcodes.ACC_INTERFACE) > 0;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
if (tmpMethodList == null) {
tmpMethodList = new ArrayList<CachedMethodMirror>();
}
tmpMethodList.add(new CachedMethodMirror(access, name, desc, exceptions));
return null; // null MethodVisitor to avoid examining the instructions.
}
public void visitEnd() {
if (tmpMethodList != null) {
declaredMethods = new MethodMirror[tmpMethodList.size()];
int i = 0;
for (MethodMirror mm: tmpMethodList) {
declaredMethods[i++] = mm;
}
tmpMethodList = null;
}
}
// Dummy methods
public void visitSource(String source, String debug) {}
public void visitOuterClass(String owner, String name, String desc) {}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return DummyAnnotationVisitor.singleton;
}
public void visitAttribute(Attribute attr) {}
public void visitInnerClass(String name, String outerName, String innerName, int access) {}
public FieldVisitor visitField(int access, String name, String desc, String signature,
Object value) {
return null;
}
static class DummyAnnotationVisitor extends AnnotationVisitor {
public DummyAnnotationVisitor() {
super(Opcodes.ASM4);
}
static DummyAnnotationVisitor singleton = new DummyAnnotationVisitor();
public void visit(String name, Object value) {}
public AnnotationVisitor visitAnnotation(String name, String desc) {return this;}
public AnnotationVisitor visitArray(String name) {return DummyAnnotationVisitor.singleton;}
public void visitEnd() {}
public void visitEnum(String name, String desc, String value) {}
}
}
class CachedMethodMirror implements MethodMirror {
private String[] exceptions;
private String desc;
private String name;
private boolean isBridge;
public CachedMethodMirror(int access, String name, String desc, String[] exceptions) {
this.name = name;
this.desc = desc;
this.exceptions = (exceptions == null) ? CachedClassMirrors.EMPTY_SET : exceptions;
isBridge = (access & Opcodes.ACC_BRIDGE) > 0;
}
public String getName() {
return name;
}
public String[] getExceptionTypes() throws ClassMirrorNotFoundException {
return exceptions;
}
public String getMethodDescriptor() {
return desc;
}
public boolean isBridge() {
return isBridge;
}
}