Now everything works; handlers are called via SPI, and annotations are being parsed. w00t!

This commit is contained in:
Reinier Zwitserloot committed 2009-06-12 09:54:24 +02:00
1 parent 40e35d6f79
commit 6372983000
7 files changed
+409 -33

No files matched your search

+4 -4
View File
@@ -18,8 +18,8 @@ public abstract class EclipseASTAdapter implements EclipseASTVisitor {
@Override public void endVisitInitializer(Node node, Initializer initializer) {}
@Override public void visitField(Node node, FieldDeclaration field) {}
@Override public void endVisitField(Node node, FieldDeclaration field) {}
@Override public void visitMethod(Node node, AbstractMethodDeclaration declaration) {}
@Override public void endVisitMethod(Node node, AbstractMethodDeclaration declaration) {}
@Override public void visitLocal(Node node, LocalDeclaration declaration) {}
@Override public void endVisitLocal(Node node, LocalDeclaration declaration) {}
@Override public void visitMethod(Node node, AbstractMethodDeclaration method) {}
@Override public void endVisitMethod(Node node, AbstractMethodDeclaration method) {}
@Override public void visitLocal(Node node, LocalDeclaration local) {}
@Override public void endVisitLocal(Node node, LocalDeclaration local) {}
}
@@ -0,0 +1,5 @@
package lombok.eclipse;
public interface EclipseAnnotationHandler<T extends java.lang.annotation.Annotation> {
void handle(T annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseAST.Node node);
}
+282
View File
@@ -0,0 +1,282 @@
package lombok.eclipse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import lombok.eclipse.EclipseAST.Node;
import lombok.transformations.TypeLibrary;
import lombok.transformations.TypeResolver;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.Literal;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
public class HandlerLibrary {
private TypeLibrary typeLibrary = new TypeLibrary();
private static class HandlerContainer<T extends Annotation> {
private EclipseAnnotationHandler<T> handler;
private Class<T> annotationClass;
HandlerContainer(EclipseAnnotationHandler<T> handler, Class<T> annotationClass) {
this.handler = handler;
this.annotationClass = annotationClass;
}
@SuppressWarnings("unchecked")
public void handle(Object annInstance,
org.eclipse.jdt.internal.compiler.ast.Annotation annotation,
Node node) {
handler.handle((T) annInstance, annotation, node);
}
}
private Map<String, HandlerContainer<?>> handlers = new HashMap<String, HandlerContainer<?>>();
@SuppressWarnings("unchecked")
public <A extends Annotation> A createAnnotation(Class<A> target,
CompilationUnitDeclaration ast,
org.eclipse.jdt.internal.compiler.ast.Annotation node) throws EnumDecodeFail {
final Map<String, Object> values = new HashMap<String, Object>();
final MemberValuePair[] pairs = node.memberValuePairs();
for ( Method m : target.getMethods() ) {
String name = m.getName();
Object value = m.getDefaultValue();
for ( MemberValuePair pair : pairs ) {
if ( name.equals(new String(pair.name)) ) {
value = calculateValue(ast, m.getReturnType(), pair.value);
break;
}
}
values.put(name, value);
}
InvocationHandler invocations = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return values.get(method.getName());
}
};
return (A) Proxy.newProxyInstance(target.getClassLoader(), new Class[] { target }, invocations);
}
private Object calculateValue(CompilationUnitDeclaration ast, Class<?> type, Expression e) throws EnumDecodeFail {
if ( e instanceof Literal ) {
((Literal)e).computeConstant();
return convertConstant(type, e.constant);
} else if ( e instanceof ArrayInitializer ) {
if ( !type.isArray() ) throw new EnumDecodeFail("Did not expect an array here.");
Class<?> component = type.getComponentType();
Expression[] expressions = ((ArrayInitializer)e).expressions;
int length = expressions == null ? 0 : expressions.length;
Object[] values = new Object[length];
for (int i = 0; i < length; i++) {
values[i] = calculateValue(ast, component, expressions[i]);
}
return values;
} else if ( e instanceof ClassLiteralAccess ) {
if ( type == Class.class ) return toClass(ast, str(((ClassLiteralAccess)e).type.getTypeName()));
else throw new EnumDecodeFail("Expected a " + type + " literal.");
} else if ( e instanceof NameReference ) {
String s = null;
if ( e instanceof SingleNameReference ) s = new String(((SingleNameReference)e).token);
else if ( e instanceof QualifiedNameReference ) s = str(((QualifiedNameReference)e).tokens);
if ( Enum.class.isAssignableFrom(type) ) toEnum(type, s);
throw new EnumDecodeFail("Lombok annotations must contain literals only.");
} else {
throw new EnumDecodeFail("Lombok could not decode this annotation parameter.");
}
}
private Enum<?> toEnum(Class<?> enumType, String ref) throws EnumDecodeFail {
int idx = ref.indexOf('.');
if ( idx > -1 ) ref = ref.substring(idx +1);
Object[] enumConstants = enumType.getEnumConstants();
for ( Object constant : enumConstants ) {
String target = ((Enum<?>)constant).name();
if ( target.equals(ref) ) return (Enum<?>) constant;
}
throw new EnumDecodeFail("I can't figure out which enum constant you mean.");
}
private Class<?> toClass(CompilationUnitDeclaration ast, String typeName) throws EnumDecodeFail {
Class<?> c;
boolean fqn = typeName.indexOf('.') > -1;
if ( fqn ) {
c = tryClass(typeName);
if ( c != null ) return c;
}
for ( ImportReference ref : ast.imports ) {
String im = str(ref.tokens);
int idx = im.lastIndexOf('.');
String simple = im;
if ( idx > -1 ) simple = im.substring(idx+1);
if ( simple.equals(typeName) ) {
c = tryClass(im);
if ( c != null ) return c;
}
}
if ( ast.currentPackage != null && ast.currentPackage.tokens != null ) {
String pkg = str(ast.currentPackage.tokens);
c = tryClass(pkg + "." + typeName);
if ( c != null ) return c;
}
c = tryClass("java.lang." + typeName);
if ( c != null ) return c;
throw new EnumDecodeFail("I can't find this class. Try using the fully qualified name.");
}
private Class<?> tryClass(String name) {
try {
return Class.forName(name);
} catch ( ClassNotFoundException e ) {
return null;
}
}
private Object convertConstant(Class<?> type, Constant constant) throws EnumDecodeFail {
int targetTypeID;
boolean array = type.isArray();
if ( array ) type = type.getComponentType();
if ( type == int.class ) targetTypeID = TypeIds.T_int;
else if ( type == long.class ) targetTypeID = TypeIds.T_long;
else if ( type == short.class ) targetTypeID = TypeIds.T_short;
else if ( type == byte.class ) targetTypeID = TypeIds.T_byte;
else if ( type == double.class ) targetTypeID = TypeIds.T_double;
else if ( type == float.class ) targetTypeID = TypeIds.T_float;
else if ( type == String.class ) targetTypeID = TypeIds.T_JavaLangString;
else if ( type == char.class ) targetTypeID = TypeIds.T_char;
else if ( type == boolean.class ) targetTypeID = TypeIds.T_boolean;
else {
//Enum or Class, so a constant isn't going to be very useful.
throw new EnumDecodeFail("Expected a constant of some sort here (a number or a string)");
}
if ( !Expression.isConstantValueRepresentable(constant, constant.typeID(), targetTypeID) ) {
throw new EnumDecodeFail("I can't turn this literal into a " + type);
}
Object o = null;
if ( type == int.class ) o = constant.intValue();
else if ( type == long.class ) o = constant.longValue();
else if ( type == short.class ) o = constant.shortValue();
else if ( type == byte.class ) o = constant.byteValue();
else if ( type == double.class ) o = constant.doubleValue();
else if ( type == float.class ) o = constant.floatValue();
else if ( type == String.class ) o = constant.stringValue();
else if ( type == char.class ) o = constant.charValue();
else if ( type == boolean.class ) o = constant.booleanValue();
if ( array ) {
Object a = Array.newInstance(type, 1);
Array.set(a, 0, o);
return a;
}
return o;
}
private static class EnumDecodeFail extends Exception {
private static final long serialVersionUID = 1L;
EnumDecodeFail(String msg) {
super(msg);
}
}
private static String str(char[][] c) {
boolean first = true;
StringBuilder sb = new StringBuilder();
for ( char[] part : c ) {
sb.append(first ? "" : ".").append(part);
first = false;
}
return sb.toString();
}
@SuppressWarnings("unchecked")
public static HandlerLibrary load() {
HandlerLibrary lib = new HandlerLibrary();
for ( EclipseAnnotationHandler<?> handler : ServiceLoader.load(EclipseAnnotationHandler.class) ) {
Class<? extends Annotation> annotationClass = lib.findAnnotationClass(handler.getClass());
HandlerContainer<?> container = new HandlerContainer(handler, annotationClass);
lib.handlers.put(container.annotationClass.getName(), container);
lib.typeLibrary.addType(container.annotationClass.getName());
}
return lib;
}
@SuppressWarnings("unchecked")
private Class<? extends Annotation> findAnnotationClass(Class<?> c) {
if ( c == Object.class || c == null ) return null;
for ( Type iface : c.getGenericInterfaces() ) {
if ( iface instanceof ParameterizedType ) {
ParameterizedType p = (ParameterizedType)iface;
if ( !EclipseAnnotationHandler.class.equals(p.getRawType()) ) continue;
Type target = p.getActualTypeArguments()[0];
if ( target instanceof Class<?> ) {
if ( Annotation.class.isAssignableFrom((Class<?>) target) ) {
return (Class<? extends Annotation>) target;
}
}
throw new ClassCastException("Not an annotation type: " + target);
}
}
Class<? extends Annotation> potential = findAnnotationClass(c.getSuperclass());
if ( potential != null ) return potential;
for ( Class<?> iface : c.getInterfaces() ) {
potential = findAnnotationClass(iface);
if ( potential != null ) return potential;
}
return null;
}
public void handle(CompilationUnitDeclaration ast, EclipseAST.Node node,
org.eclipse.jdt.internal.compiler.ast.Annotation annotation) {
TypeResolver resolver = new TypeResolver(typeLibrary, node.top());
TypeReference rawType = annotation.type;
if ( rawType == null ) return;
for ( String fqn : resolver.findTypeMatches(node, annotation.type) ) {
HandlerContainer<?> container = handlers.get(fqn);
if ( container == null ) continue;
try {
Object annInstance = createAnnotation(container.annotationClass, ast, annotation);
container.handle(annInstance, annotation, node);
} catch (EnumDecodeFail e) {
e.printStackTrace();
//TODO: Add to problems array in ast.
}
}
}
}
+75 -14
View File
@@ -1,15 +1,15 @@
package lombok.eclipse;
import java.util.Map;
import java.util.WeakHashMap;
import java.lang.reflect.Field;
import lombok.eclipse.EclipseAST.Node;
import lombok.eclipse.handlers.HandleGetter_ecj;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.parser.Parser;
/**
@@ -26,8 +26,27 @@ import org.eclipse.jdt.internal.compiler.parser.Parser;
* @author rspilker
*/
public class TransformEclipseAST {
private static final Map<CompilationUnitDeclaration, EclipseAST> astCache =
new WeakHashMap<CompilationUnitDeclaration, EclipseAST>();
private final EclipseAST ast;
//The patcher hacks this field onto CUD. It's public.
private static final Field astCacheField;
private static final HandlerLibrary handlers;
static {
Field f = null;
HandlerLibrary l = null;
try {
l = HandlerLibrary.load();
} catch ( Exception e ) {
e.printStackTrace();
}
try {
f = CompilationUnitDeclaration.class.getDeclaredField("$lombokAST");
} catch ( NoSuchFieldException ignore ) {
ignore.printStackTrace();
}
astCacheField = f;
handlers = l;
}
/**
* This method is called immediately after eclipse finishes building a CompilationUnitDeclaration, which is
@@ -41,25 +60,67 @@ public class TransformEclipseAST {
* @param ast The AST node belonging to the compilation unit (java speak for a single source file).
*/
public static void transform(Parser parser, CompilationUnitDeclaration ast) {
EclipseAST existing = astCache.get(ast);
EclipseAST existing = getCache(ast);
if ( existing == null ) {
existing = new EclipseAST(ast);
astCache.put(ast, existing);
setCache(ast, existing);
} else existing.reparse();
existing.traverse(new AnnotationVisitor());
new TransformEclipseAST(existing).go();
}
private static EclipseAST getCache(CompilationUnitDeclaration ast) {
if ( astCacheField == null ) return null;
try {
return (EclipseAST)astCacheField.get(ast);
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
private static void setCache(CompilationUnitDeclaration ast, EclipseAST cache) {
if ( astCacheField != null ) try {
astCacheField.set(ast, cache);
} catch ( Exception ignore ) {
ignore.printStackTrace();
}
}
public TransformEclipseAST(EclipseAST ast) {
this.ast = ast;
}
public void go() {
ast.traverse(new AnnotationVisitor());
}
private static class AnnotationVisitor extends EclipseASTAdapter {
@Override public void visitField(Node node, FieldDeclaration field) {
if ( field.annotations == null ) return;
for ( Annotation annotation : field.annotations ) {
TypeReference type = annotation.type;
if ( type != null && new String(type.getLastToken()).equals("Getter") ) {
new HandleGetter_ecj().apply(annotation, node, field);
}
handlers.handle((CompilationUnitDeclaration) node.top().node, node, annotation);
}
}
@Override public void visitLocal(Node node, LocalDeclaration local) {
if ( local.annotations == null ) return;
for ( Annotation annotation : local.annotations ) {
handlers.handle((CompilationUnitDeclaration) node.top().node, node, annotation);
}
}
@Override public void visitMethod(Node node, AbstractMethodDeclaration method) {
if ( method.annotations == null ) return;
for ( Annotation annotation : method.annotations ) {
handlers.handle((CompilationUnitDeclaration) node.top().node, node, annotation);
}
}
@Override public void visitType(Node node, TypeDeclaration type) {
if ( type.annotations == null ) return;
for ( Annotation annotation : type.annotations ) {
handlers.handle((CompilationUnitDeclaration) node.top().node, node, annotation);
}
}
}
}
@@ -2,6 +2,9 @@ package lombok.eclipse.handlers;
import java.lang.reflect.Modifier;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseAST.Node;
import lombok.transformations.TransformationsUtil;
@@ -17,9 +20,12 @@ import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.mangosdk.spi.ProviderFor;
public class HandleGetter_ecj {
public void apply(Annotation annotation, Node node, FieldDeclaration field) {
@ProviderFor(EclipseAnnotationHandler.class)
public class HandleGetter_ecj implements EclipseAnnotationHandler<Getter> {
@Override public void handle(Getter annotation, Annotation ast, Node node) {
FieldDeclaration field = (FieldDeclaration) node.getEclipseNode();
TypeReference fieldType = field.type;
String getterName = TransformationsUtil.toGetterName(
new String(field.name), nameEquals(fieldType.getTypeName(), "boolean"));
@@ -30,7 +36,7 @@ public class HandleGetter_ecj {
}
MethodDeclaration method = new MethodDeclaration(parent.compilationResult);
method.modifiers = Modifier.PUBLIC;
method.modifiers = toModifier(annotation.value());
method.returnType = field.type;
method.annotations = null;
method.arguments = null;
@@ -42,8 +48,8 @@ public class HandleGetter_ecj {
method.bits |= ASTNode.Bit24;
Expression fieldExpression = new SingleNameReference(field.name, (field.declarationSourceStart << 32) | field.declarationSourceEnd);
Statement returnStatement = new ReturnStatement(fieldExpression, field.sourceStart, field.sourceEnd);
method.bodyStart = method.declarationSourceStart = method.sourceStart = annotation.sourceStart;
method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = annotation.sourceEnd;
method.bodyStart = method.declarationSourceStart = method.sourceStart = ast.sourceStart;
method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = ast.sourceEnd;
method.statements = new Statement[] { returnStatement };
if ( parent.methods == null ) {
parent.methods = new AbstractMethodDeclaration[1];
@@ -56,6 +62,21 @@ public class HandleGetter_ecj {
}
}
private int toModifier(AccessLevel value) {
switch ( value ) {
case MODULE:
case PACKAGE:
return 0;
default:
case PUBLIC:
return Modifier.PUBLIC;
case PROTECTED:
return Modifier.PROTECTED;
case PRIVATE:
return Modifier.PRIVATE;
}
}
private boolean nameEquals(char[][] typeName, String string) {
StringBuilder sb = new StringBuilder();
boolean first = true;
@@ -67,4 +88,5 @@ public class HandleGetter_ecj {
return string.contentEquals(sb);
}
}
+2 -1
View File
@@ -33,6 +33,7 @@ public class TypeLibrary {
}
public Collection<String> findCompatible(String typeReference) {
return simpleToQualifiedMap.get(typeReference);
Set<String> result = simpleToQualifiedMap.get(typeReference);
return result == null ? Collections.<String>emptySet() : result;
}
}
+14 -9
View File
@@ -6,23 +6,21 @@ import java.util.HashSet;
import java.util.Set;
import lombok.eclipse.EclipseAST;
import lombok.eclipse.EclipseAST.Node;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.parser.Parser;
public class TypeResolver {
private final TypeLibrary library;
private final EclipseAST ast;
private Collection<String> imports;
public TypeResolver(TypeLibrary library, Parser parser, EclipseAST ast) {
public TypeResolver(TypeLibrary library, EclipseAST.Node top) {
this.library = library;
this.ast = ast;
this.imports = makeImportList((CompilationUnitDeclaration) ast.top().getEclipseNode());
this.imports = makeImportList((CompilationUnitDeclaration) top.getEclipseNode());
}
private static Collection<String> makeImportList(CompilationUnitDeclaration declaration) {
@@ -33,8 +31,8 @@ public class TypeResolver {
}
return imports;
}
public Collection<String> findTypeMatches(ASTNode context, TypeReference type) {
public Collection<String> findTypeMatches(Node context, TypeReference type) {
Collection<String> potentialMatches = library.findCompatible(toQualifiedName(type.getTypeName()));
if ( potentialMatches.isEmpty() ) return Collections.emptyList();
@@ -50,7 +48,14 @@ public class TypeResolver {
if ( potentialMatches.isEmpty() ) return Collections.emptyList();
//Find a lexically accessible type of the same simple name in the same Compilation Unit. If it exists: no matches.
Node n = context;
while ( n != null ) {
if ( n.getEclipseNode() instanceof TypeDeclaration ) {
char[] name = ((TypeDeclaration)n.getEclipseNode()).name;
if ( name != null && new String(name).equals(simpleName) ) return Collections.emptyList();
}
n = n.up();
}
// The potential matches we found by comparing the import statements is our matching set. Return it.
return potentialMatches;