package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.laytonsmith.PureUtilities.Common.StringUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import static org.objectweb.asm.Opcodes.*;
public class ClassMirrorVisitor extends ClassVisitor {
private final ClassMirror.ClassInfo classInfo;
ClassMirrorVisitor(ClassMirror.ClassInfo info) {
super(Opcodes.ASM5);
this.classInfo = info;
}
public ClassMirrorVisitor() {
this(new ClassMirror.ClassInfo());
}
public ClassMirror<?> getMirror(URL source) {
if(!done) {
throw new IllegalStateException(String.format(
"Not done visiting %s", classInfo.name == null ? "none" : classInfo.name
));
}
return new ClassMirror<Object>(this.classInfo, source);
}
private boolean done;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if(done) {
// Ensure we never visit more than one class
throw new IllegalStateException(String.format(
"Can't visit %s, because we already visited %s", name, classInfo.name
));
}
if ((access & ACC_ENUM) == ACC_ENUM) classInfo.isEnum = true;
if ((access & ACC_INTERFACE) == ACC_INTERFACE) classInfo.isInterface = true;
classInfo.modifiers = new ModifierMirror(ModifierMirror.Type.CLASS, access);
classInfo.name = name;
classInfo.classReferenceMirror = new ClassReferenceMirror(Type.getObjectType(name).getDescriptor());
classInfo.superClass = superName;
classInfo.interfaces = interfaces;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
final AnnotationMirror mirror = new AnnotationMirror(new ClassReferenceMirror(desc), visible);
return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), mirror) {
@Override
public void visitEnd() {
classInfo.annotations.add(mirror);
super.visitEnd();
}
};
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
final FieldMirror fieldMirror = new FieldMirror(
classInfo.classReferenceMirror,
new ModifierMirror(ModifierMirror.Type.FIELD, access),
new ClassReferenceMirror(desc),
name,
value
);
return new FieldVisitor(ASM5, super.visitField(access, name, desc, signature, value)) {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
final AnnotationMirror annotationMirror = new AnnotationMirror(new ClassReferenceMirror(desc), visible);
return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), annotationMirror) {
@Override
public void visitEnd() {
fieldMirror.addAnnotation(annotationMirror);
super.visitEnd();
}
};
}
@Override
public void visitEnd() {
classInfo.fields.add(fieldMirror);
super.visitEnd();
}
};
}
private static final Pattern STATIC_INITIALIZER_PATTERN = Pattern.compile("<clinit>");
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (STATIC_INITIALIZER_PATTERN.matcher(name).matches()) return null; // Ignore static initializers
if(ConstructorMirror.INIT.matches(name)){
// We want to replace the V at the end with the parent class type.
// Yes, technically a constructor really does return void, but.. not really.
desc = StringUtils.replaceLast(desc, "V", classInfo.classReferenceMirror.getJVMName());
}
List<ClassReferenceMirror> parameterMirrors = new ArrayList<>();
for (Type type : Type.getArgumentTypes(desc)) {
parameterMirrors.add(new ClassReferenceMirror(type.getDescriptor()));
}
AbstractMethodMirror _methodMirror;
if(ConstructorMirror.INIT.equals(name)){
_methodMirror = new ConstructorMirror(
classInfo.classReferenceMirror,
new ModifierMirror(ModifierMirror.Type.METHOD, access),
new ClassReferenceMirror(Type.getReturnType(desc).getDescriptor()),
name,
parameterMirrors,
(access & ACC_VARARGS) == ACC_VARARGS,
(access & ACC_SYNTHETIC) == ACC_SYNTHETIC
);
} else {
_methodMirror = new MethodMirror(
classInfo.classReferenceMirror,
new ModifierMirror(ModifierMirror.Type.METHOD, access),
new ClassReferenceMirror(Type.getReturnType(desc).getDescriptor()),
name,
parameterMirrors,
(access & ACC_VARARGS) == ACC_VARARGS,
(access & ACC_SYNTHETIC) == ACC_SYNTHETIC
);
}
final AbstractMethodMirror methodMirror = _methodMirror;
return new MethodVisitor(ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
final AnnotationMirror annotationMirror = new AnnotationMirror(new ClassReferenceMirror<>(desc), visible);
return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), annotationMirror) {
@Override
public void visitEnd() {
methodMirror.addAnnotation(annotationMirror);
}
};
}
@Override
public void visitEnd() {
classInfo.methods.add(methodMirror);
super.visitEnd();
}
};
}
@Override
public void visitEnd() {
this.done = true;
super.visitEnd();
}
private static class AnnotationMirrorVisitor extends AnnotationVisitor {
private final AnnotationMirror mirror;
public AnnotationMirrorVisitor(AnnotationVisitor next, AnnotationMirror mirror) {
super(ASM5, next);
this.mirror = mirror;
}
@Override
public void visit(String name, Object value) {
if (value instanceof Type) value = ((Type) value).getDescriptor();
mirror.addAnnotationValue(name, value);
super.visit(name, value);
}
}
}