// This class is a complete ClassVisitor with many hidden classes that do
// the work of reading annotations from a class file and inserting them into
// an AScene.
package annotations.io.classfile;
/*>>>
import org.checkerframework.checker.nullness.qual.*;
*/
import java.io.File;
import java.util.*;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypeAnnotationVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;
import annotations.*;
import annotations.el.*;
import annotations.field.*;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
/**
* A ClassAnnotationSceneReader is a
* {@link org.objectweb.asm.ClassVisitor} that will insert all annotations it
* encounters while visiting a class into a given {@link AScene}.
*
* The "read" in ClassAnnotationSceneReader refers to a class
* file being read into a scene. Also see {@link ClassAnnotationSceneWriter}.
*
*
*
* The proper usage of this class is to construct a
* ClassAnnotationSceneReader} with an {@link AScene} into which
* annotations should be inserted, then pass this as a
* {@link org.objectweb.asm.ClassVisitor} to
* {@link org.objectweb.asm.ClassReader#accept}
*
*
*
* All other methods are intended to be called only by
* {@link org.objectweb.asm.ClassReader#accept},
* and should not be called anywhere else, due to the order in which
* {@link org.objectweb.asm.ClassVisitor} methods should be called.
*/
public class ClassAnnotationSceneReader
extends EmptyVisitor {
// general strategy:
// -only "Runtime[In]visible[Type]Annotations" are supported
// -use an empty visitor for everything besides annotations, fields and
// methods; for those three, use a special visitor that does all the work
// and inserts the annotations correctly into the specified AElement
// Whether to output tracing information
private static final boolean trace = false;
// Whether to output error messages for unsupported cases
private static final boolean strict = false;
// Whether to include annotations on compiler-generated methods
private final boolean ignoreBridgeMethods;
// The scene into which this class will insert annotations.
private final AScene scene;
// The AClass that represents this class in scene.
private AClass aClass;
private final ClassReader cr;
/**
* Holds definitions we've seen so far. Maps from annotation name to
* the definition itself. Maps from both the qualified name and the
* unqualified name. If the unqualified name is not unique, it maps
* to null and the qualified name should be used instead. */
private final Map adefs = initAdefs();
private static Map initAdefs() {
Map result = new HashMap();
for (AnnotationDef ad : Annotations.standardDefs) {
result.put(ad.name, ad);
}
return result;
}
/**
* constructs a new ClassAnnotationSceneReader that will
* insert all the annotations in the class that it visits into
* scene
* @param cr
*
* @param scene the annotation scene into which annotations this visits
* will be inserted
* @param ignoreBridgeMethods whether to omit annotations on
* compiler-generated methods
*/
public ClassAnnotationSceneReader(ClassReader cr, AScene scene,
boolean ignoreBridgeMethods) {
this.cr = cr;
this.scene = scene;
this.ignoreBridgeMethods = ignoreBridgeMethods;
}
/**
* @see org.objectweb.asm.commons.EmptyVisitor#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
aClass = scene.classes.vivify(name.replace('/', '.'));
}
/**
* @see org.objectweb.asm.commons.EmptyVisitor#visitAnnotation(java.lang.String, boolean)
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (trace) { System.out.printf("visitAnnotation(%s, %s) in %s (%s)%n", desc, visible, this, this.getClass()); }
return visitTypeAnnotation(desc, visible, false);
}
/**
* @see org.objectweb.asm.commons.EmptyVisitor#visitTypeAnnotation(java.lang.String, boolean, boolean)
*/
@Override
public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible, boolean inCode) {
if (trace) { System.out.printf("visitTypeAnnotation(%s, %s, %s); aClass=%s in %s (%s)%n", desc, inCode, visible, aClass, this, this.getClass()); }
return new AnnotationSceneReader(desc, visible, aClass);
}
/**
* @see org.objectweb.asm.commons.EmptyVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
*/
@Override
public FieldVisitor visitField(
int access,
String name,
String desc,
String signature,
Object value ) {
if (trace) { System.out.printf("visitField(%s, %s, %s, %s, %s) in %s (%s)%n", access, name, desc, signature, value, this, this.getClass()); }
AField aField = aClass.fields.vivify(name);
return new FieldAnnotationSceneReader(name, desc, signature, value, aField);
}
/**
* @see org.objectweb.asm.commons.EmptyVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
@Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (ignoreBridgeMethods && (access & Opcodes.ACC_BRIDGE) != 0) {
return null;
}
if (trace) { System.out.printf("visitMethod(%s, %s, %s, %s, %s) in %s (%s)%n", access, name, desc, signature, exceptions, this, this.getClass()); }
AMethod aMethod = aClass.methods.vivify(name+desc);
return new MethodAnnotationSceneReader(name, desc, signature, aMethod);
}
// converts JVML format to Java format
private static String classDescToName(String desc) {
return desc.substring(1, desc.length() - 1).replace('/', '.');
}
///////////////////////////////////////////////////////////////////////////
/// Inner classes
///
// Hackish workaround for odd subclassing.
@SuppressWarnings("signature")
String dummyDesc = "dummy";
/*
* Most of the complexity behind reading annotations from a class file into
* a scene is in AnnotationSceneReader, which fully implements the
* TypeAnnotationVisitor interface (and therefore also implements the
* AnnotationVisitor interface). It keeps an AElement of the
* element into which this should insert the annotations it visits in
* a class file. Thus, constructing an AnnotationSceneReader with an
* AElement of the right type is sufficient for writing out annotations
* to that element, which will be done once visitEnd() is called. Note that
* for when inner annotations are expected, the aElement passed in must be
* of the correct form (ATypeElement, or AMethod depending on the
* target type of the extended annotation).
*/
private class AnnotationSceneReader implements TypeAnnotationVisitor {
// Implementation strategy:
// For field values and enums, simply pass the information
// onto annotationBuilder.
// For arrays, use an ArrayAnnotationBuilder that will
// properly call the right annotationBuilder methods on its visitEnd().
// For nested annotations, use a NestedAnnotationSceneReader that will
// properly call the right annotationBuilder methods on its visitEnd().
// For extended information, store all arguments passed in and on
// this.visitEnd(), handle all the information based on target type.
// The AElement into which the annotation visited should be inserted.
protected AElement aElement;
// Whether or not this annotation is visible at runtime.
protected boolean visible;
// The AnnotationBuilder used to create this annotation.
private AnnotationBuilder annotationBuilder;
// since AnnotationSceneReader will work for both normal
// and extended annotations, all of the following information
// may or may not be present, so use a list to store
// information as it is received from visitX* methods, and
// correctly interpret the information in visitEnd()
// note that all of these should contain 0 or 1 elements,
// except for xLocations, which is actually a list
private final List xTargetTypeArgs;
private final List xIndexArgs;
private final List xLengthArgs;
private final List xLocationsArgs;
private final List xLocationLengthArgs;
private final List xOffsetArgs;
private final List xStartPcArgs;
private final List xParamIndexArgs;
private final List xBoundIndexArgs;
private final List xExceptionIndexArgs;
private final List xTypeIndexArgs;
// private AnnotationDef getAnnotationDef(Object o) {
// if (o instanceof AnnotationDef) {
// return (AnnotationDef) o;
// } else if (o instanceof String) {
// return getAnnotationDef((String) o);
// } else {
// throw new Error(String.format("bad type %s : %s", o.getClass(), o));
// }
// }
@SuppressWarnings("unchecked")
private AnnotationDef getAnnotationDef(String jvmlClassName) {
String annoTypeName = classDescToName(jvmlClassName);
// It would be better to not require the .class file to be on the
// classpath, but to search for it on a path that is passed to this
// program. Worry about that later.
Class extends java.lang.annotation.Annotation> annoClass;
try {
annoClass = (Class extends java.lang.annotation.Annotation>) Class.forName(annoTypeName);
} catch (ClassNotFoundException e) {
System.out.printf("Could not find class: %s%n", e.getMessage());
printClasspath();
if (annoTypeName.contains("+")) {
return Annotations.createValueAnnotationDef(annoTypeName,
Annotations.noAnnotations, BasicAFT.forType(int.class));
}
throw new Error(e);
}
AnnotationDef ad = AnnotationDef.fromClass(annoClass, adefs);
return ad;
}
/*
* Constructs a new AnnotationScene reader with the given description and
* visibility. Calling visitEnd() will ensure that this writes out the
* annotation it visits into aElement.
* @param desc JVML format for the field being read, or ClassAnnotationSceneReader.dummyDesc
*/
public AnnotationSceneReader(String desc, boolean visible, AElement aElement) {
if (trace) { System.out.printf("AnnotationSceneReader(%s, %s, %s)%n", desc, visible, aElement); }
this.visible = visible;
this.aElement = aElement;
if (desc != dummyDesc) { // interned
AnnotationDef ad = getAnnotationDef(desc);
AnnotationBuilder ab = AnnotationFactory.saf.beginAnnotation(ad);
if (ab == null) {
throw new IllegalArgumentException("bad description: " + desc);
} else {
this.annotationBuilder = ab;
}
}
// For legal annotations, and except for xLocationsArgs, these should
// contain at most one element.
this.xTargetTypeArgs = new ArrayList(1);
this.xIndexArgs = new ArrayList(1);
this.xLengthArgs = new ArrayList(1);
this.xLocationLengthArgs = new ArrayList(1);
this.xOffsetArgs = new ArrayList(1);
this.xStartPcArgs = new ArrayList(1);
this.xLocationsArgs = new ArrayList();
this.xParamIndexArgs = new ArrayList(1);
this.xBoundIndexArgs = new ArrayList(1);
this.xExceptionIndexArgs = new ArrayList(1);
this.xTypeIndexArgs = new ArrayList(1);
}
/*
* @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
*/
@Override
public void visit(String name, Object value) {
if (trace) { System.out.printf("visit(%s, %s) on %s%n", name, value, this); }
// BasicAFT.forType(Class) expects int.class instead of Integer.class,
// and so on for all primitives. String.class is ok, since it has no
// primitive type.
Class> c = value.getClass();
if (c.equals(Boolean.class)) {
c = boolean.class;
} else if (c.equals(Byte.class)) {
c = byte.class;
} else if (c.equals(Character.class)) {
c = char.class;
} else if (c.equals(Short.class)) {
c = short.class;
} else if (c.equals(Integer.class)) {
c = int.class;
} else if (c.equals(Long.class)) {
c = long.class;
} else if (c.equals(Float.class)) {
c = float.class;
} else if (c.equals(Double.class)) {
c = double.class;
} else if (c.equals(Type.class)) {
try {
annotationBuilder.addScalarField(name, ClassTokenAFT.ctaft, Class.forName(((Type)value).getClassName()));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load Class for Type: " + value, e);
}
// Return here, otherwise the annotationBuilder would be called
// twice for the same value.
return;
} else if (!c.equals(String.class)) {
// Only possible type for value is String, in which case c is already
// String.class, or array of primitive
c = c.getComponentType();
ArrayBuilder arrayBuilder = annotationBuilder.beginArrayField(
name, new ArrayAFT(BasicAFT.forType(c)));
// value is of type c[], now add in all the elements of the array
for (Object o : asList(value)) {
arrayBuilder.appendElement(o);
}
arrayBuilder.finish();
return;
}
// handle everything but arrays
annotationBuilder.addScalarField(name, BasicAFT.forType(c),value);
}
/*
* Method that accepts an Object whose actual type is c[], where c is a
* primitive, and returns an equivalent List