package org.jetbrains.ether.dependencyView;
import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
import org.jetbrains.ether.Pair;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.EmptyVisitor;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: db
* Date: 31.01.11
* Time: 2:00
* To change this template use File | Settings | File Templates.
*/
public class ClassfileAnalyzer {
private static class Holder<T> {
private T x = null;
public void set(final T x) {
this.x = x;
}
public T get() {
return x;
}
}
private static class ClassCrawler extends EmptyVisitor {
private class AnnotationRetentionPolicyCrawler implements AnnotationVisitor {
public void visit(String name, Object value) {
}
public void visitEnum(String name, String desc, String value) {
policy = RetentionPolicy.valueOf(value);
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
return null;
}
public AnnotationVisitor visitArray(String name) {
return null;
}
public void visitEnd() {
}
}
private class AnnotationTargetCrawler implements AnnotationVisitor {
public void visit(String name, Object value) {
}
public void visitEnum(final String name, String desc, final String value) {
targets.add(ElementType.valueOf(value));
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
return this;
}
public AnnotationVisitor visitArray(String name) {
return this;
}
public void visitEnd() {
}
}
private class AnnotationCrawler implements AnnotationVisitor {
private final TypeRepr.ClassType type;
private final ElementType target;
private final Set<StringCache.S> usedArguments = new HashSet<StringCache.S>();
private AnnotationCrawler(final TypeRepr.ClassType type, final ElementType target) {
this.type = type;
this.target = target;
annotationTargets.put(type, target);
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(type.className));
}
private String getMethodDescr(final Object value) {
if (value instanceof Type) {
return "()Ljava/lang/Class;";
}
final String name = Type.getType(value.getClass()).getInternalName();
if (name.equals("java/lang/Integer")) {
return "()I;";
}
if (name.equals("java/lang/Short")) {
return "()S;";
}
if (name.equals("java/lang/Long")) {
return "()J;";
}
if (name.equals("java/lang/Byte")) {
return "()B;";
}
if (name.equals("java/lang/Char")) {
return "()C;";
}
if (name.equals("java/lang/Boolean")) {
return "()Z;";
}
if (name.equals("java/lang/Float")) {
return "()F;";
}
if (name.equals("java/lang/Double")) {
return "()D;";
}
final String s = "()L" + name + ";";
return s;
}
public void visit(String name, Object value) {
usages.addUsage(classNameHolder.get(), UsageRepr.createMethodUsage(name, type.className.value, getMethodDescr(value)));
usedArguments.add(StringCache.get(name));
}
public void visitEnum(String name, String desc, String value) {
usages.addUsage(classNameHolder.get(), UsageRepr.createMethodUsage(name, type.className.value, "()" + desc));
usedArguments.add(StringCache.get(name));
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
return new AnnotationCrawler((TypeRepr.ClassType) TypeRepr.getType(desc), target);
}
public AnnotationVisitor visitArray(String name) {
usedArguments.add(StringCache.get(name));
return this;
}
public void visitEnd() {
final Set<StringCache.S> s = annotationArguments.get(type);
if (s == null) {
annotationArguments.put(type, usedArguments);
} else {
s.retainAll(usedArguments);
}
}
}
private void processSignature(final String sig) {
if (sig != null)
new SignatureReader(sig).accept(signatureCrawler);
}
private final SignatureVisitor signatureCrawler = new SignatureVisitor() {
public void visitFormalTypeParameter(String name) {
return;
}
public SignatureVisitor visitClassBound() {
return this;
}
public SignatureVisitor visitInterfaceBound() {
return this;
}
public SignatureVisitor visitSuperclass() {
return this;
}
public SignatureVisitor visitInterface() {
return this;
}
public SignatureVisitor visitParameterType() {
return this;
}
public SignatureVisitor visitReturnType() {
return this;
}
public SignatureVisitor visitExceptionType() {
return this;
}
public void visitBaseType(char descriptor) {
return;
}
public void visitTypeVariable(String name) {
return;
}
public SignatureVisitor visitArrayType() {
return this;
}
public void visitInnerClassType(String name) {
return;
}
public void visitTypeArgument() {
return;
}
public SignatureVisitor visitTypeArgument(char wildcard) {
return this;
}
public void visitEnd() {
}
public void visitClassType(String name) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(name));
}
};
Boolean takeIntoAccount = false;
final StringCache.S fileName;
int access;
StringCache.S name;
String superClass;
String[] interfaces;
String signature;
StringCache.S sourceFile;
final Holder<String> classNameHolder = new Holder<String>();
final Set<MethodRepr> methods = new HashSet<MethodRepr>();
final Set<FieldRepr> fields = new HashSet<FieldRepr>();
final List<String> nestedClasses = new ArrayList<String>();
final UsageRepr.Cluster usages = new UsageRepr.Cluster();
final Set<UsageRepr.Usage> annotationUsages = new HashSet<UsageRepr.Usage>();
final Set<ElementType> targets = new HashSet<ElementType>();
RetentionPolicy policy = null;
private static FoxyMap.CollectionConstructor<ElementType> elementTypeSetConstructor = new FoxyMap.CollectionConstructor<ElementType>() {
public Collection<ElementType> create() {
return new HashSet<ElementType>();
}
};
final Map<TypeRepr.ClassType, Set<StringCache.S>> annotationArguments = new HashMap<TypeRepr.ClassType, Set<StringCache.S>>();
final FoxyMap<TypeRepr.ClassType, ElementType> annotationTargets = new FoxyMap<TypeRepr.ClassType, ElementType>(elementTypeSetConstructor);
public ClassCrawler(final StringCache.S fn) {
fileName = fn;
}
private boolean notPrivate(final int access) {
return (access & Opcodes.ACC_PRIVATE) == 0;
}
public Pair<ClassRepr, Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>> getResult() {
final ClassRepr repr = takeIntoAccount ?
new ClassRepr(access, sourceFile, fileName, name, signature, superClass, interfaces, nestedClasses, fields, methods, targets, policy) : null;
if (repr != null) {
repr.updateClassUsages(usages.getUsages());
}
return new Pair<ClassRepr, Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>>(repr, new Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>(usages, annotationUsages));
}
@Override
public void visit(int version, int a, String n, String sig, String s, String[] i) {
takeIntoAccount = notPrivate(a);
access = a;
name = StringCache.get(n);
signature = sig;
superClass = s;
interfaces = i;
classNameHolder.set(n);
if (superClass != null) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(StringCache.get(superClass)));
usages.addUsage(classNameHolder.get(), UsageRepr.createClassExtendsUsage(StringCache.get(superClass)));
}
if (interfaces != null) {
for (String it : interfaces) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(StringCache.get(it)));
usages.addUsage(classNameHolder.get(), UsageRepr.createClassExtendsUsage(StringCache.get(it)));
}
}
processSignature(sig);
}
@Override
public void visitEnd() {
for (TypeRepr.ClassType type : annotationTargets.keySet()) {
final Collection<ElementType> targets = annotationTargets.foxyGet(type);
final Set<StringCache.S> usedArguments = annotationArguments.get(type);
annotationUsages.add(UsageRepr.createAnnotationUsage(type, usedArguments, targets));
}
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
if (desc.equals("Ljava/lang/annotation/Target;")) {
return new AnnotationTargetCrawler();
}
if (desc.equals("Ljava/lang/annotation/Retention;")) {
return new AnnotationRetentionPolicyCrawler();
}
return new AnnotationCrawler((TypeRepr.ClassType) TypeRepr.getType(desc), (access & Opcodes.ACC_ANNOTATION) > 0 ? ElementType.ANNOTATION_TYPE : ElementType.TYPE);
}
@Override
public void visitSource(String source, String debug) {
sourceFile = StringCache.get(source);
}
@Override
public FieldVisitor visitField(int access, String n, String desc, String signature, Object value) {
processSignature(signature);
if (notPrivate(access)) {
fields.add(new FieldRepr(access, n, desc, signature, value));
}
return new EmptyVisitor() {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return new AnnotationCrawler((TypeRepr.ClassType) TypeRepr.getType(desc), ElementType.FIELD);
}
};
}
@Override
public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions) {
final Holder<Object> defaultValue = new Holder<Object>();
processSignature(signature);
return new EmptyVisitor() {
@Override
public void visitEnd() {
if (notPrivate(access)) {
methods.add(new MethodRepr(access, n, signature, desc, exceptions, defaultValue.get()));
}
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return new AnnotationCrawler((TypeRepr.ClassType) TypeRepr.getType(desc), n.equals("<init>") ? ElementType.CONSTRUCTOR : ElementType.METHOD);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new EmptyVisitor() {
public void visit(String name, Object value) {
defaultValue.set(value);
}
};
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
return new AnnotationCrawler((TypeRepr.ClassType) TypeRepr.getType(desc), ElementType.PARAMETER);
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
final TypeRepr.ArrayType typ = (TypeRepr.ArrayType) TypeRepr.getType(desc);
final TypeRepr.AbstractType element = typ.getDeepElementType();
if (element instanceof TypeRepr.ClassType) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(((TypeRepr.ClassType) element).className));
usages.addUsage(classNameHolder.get(), UsageRepr.createClassNewUsage(((TypeRepr.ClassType) element).className));
}
typ.updateClassUsages(usages.getUsages());
super.visitMultiANewArrayInsn(desc, dims);
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
processSignature(signature);
TypeRepr.getType(desc).updateClassUsages(usages.getUsages());
super.visitLocalVariable(name, desc, signature, start, end, index);
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (type != null) {
TypeRepr.createClassType(type).updateClassUsages(usages.getUsages());
}
super.visitTryCatchBlock(start, end, handler, type);
}
@Override
public void visitTypeInsn(int opcode, String type) {
final TypeRepr.AbstractType typ = type.startsWith("[") ? TypeRepr.getType(type) : TypeRepr.createClassType(type);
if (opcode == Opcodes.NEW) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(((TypeRepr.ClassType) typ).className));
usages.addUsage(classNameHolder.get(), UsageRepr.createClassNewUsage(((TypeRepr.ClassType) typ).className));
} else if (opcode == Opcodes.ANEWARRAY) {
if (typ instanceof TypeRepr.ClassType) {
usages.addUsage(classNameHolder.get(), UsageRepr.createClassUsage(((TypeRepr.ClassType) typ).className));
usages.addUsage(classNameHolder.get(), UsageRepr.createClassNewUsage(((TypeRepr.ClassType) typ).className));
}
}
typ.updateClassUsages(usages.getUsages());
super.visitTypeInsn(opcode, type);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
usages.addUsage(classNameHolder.get(), UsageRepr.createFieldAssignUsage(name, owner, desc));
}
usages.addUsage(classNameHolder.get(), UsageRepr.createFieldUsage(name, owner, desc));
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
usages.addUsage(classNameHolder.get(), UsageRepr.createMethodUsage(name, owner, desc));
super.visitMethodInsn(opcode, owner, name, desc);
}
};
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (outerName != null && outerName.equals(name) && notPrivate(access)) {
nestedClasses.add(innerName);
}
}
}
public static Pair<ClassRepr, Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>> analyze(final StringCache.S fileName, final ClassReader cr) {
final ClassCrawler visitor = new ClassCrawler(fileName);
cr.accept(visitor, 0);
return visitor.getResult();
}
}