diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lombok/eclipse/EclipseASTAdapter.java | 8 | ||||
-rw-r--r-- | src/lombok/eclipse/EclipseAnnotationHandler.java | 5 | ||||
-rw-r--r-- | src/lombok/eclipse/HandlerLibrary.java | 282 | ||||
-rw-r--r-- | src/lombok/eclipse/TransformEclipseAST.java | 89 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleGetter_ecj.java | 32 | ||||
-rw-r--r-- | src/lombok/transformations/TypeLibrary.java | 3 | ||||
-rw-r--r-- | src/lombok/transformations/TypeResolver.java | 23 |
7 files changed, 409 insertions, 33 deletions
diff --git a/src/lombok/eclipse/EclipseASTAdapter.java b/src/lombok/eclipse/EclipseASTAdapter.java index fa87872f..a76380cf 100644 --- a/src/lombok/eclipse/EclipseASTAdapter.java +++ b/src/lombok/eclipse/EclipseASTAdapter.java @@ -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) {} } diff --git a/src/lombok/eclipse/EclipseAnnotationHandler.java b/src/lombok/eclipse/EclipseAnnotationHandler.java new file mode 100644 index 00000000..e760917b --- /dev/null +++ b/src/lombok/eclipse/EclipseAnnotationHandler.java @@ -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); +} diff --git a/src/lombok/eclipse/HandlerLibrary.java b/src/lombok/eclipse/HandlerLibrary.java new file mode 100644 index 00000000..01b2d021 --- /dev/null +++ b/src/lombok/eclipse/HandlerLibrary.java @@ -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. + } + } + } +} diff --git a/src/lombok/eclipse/TransformEclipseAST.java b/src/lombok/eclipse/TransformEclipseAST.java index d0685d1d..34350597 100644 --- a/src/lombok/eclipse/TransformEclipseAST.java +++ b/src/lombok/eclipse/TransformEclipseAST.java @@ -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); + } + } } } diff --git a/src/lombok/eclipse/handlers/HandleGetter_ecj.java b/src/lombok/eclipse/handlers/HandleGetter_ecj.java index e61eb64b..9ce8d2d9 100644 --- a/src/lombok/eclipse/handlers/HandleGetter_ecj.java +++ b/src/lombok/eclipse/handlers/HandleGetter_ecj.java @@ -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); } + } diff --git a/src/lombok/transformations/TypeLibrary.java b/src/lombok/transformations/TypeLibrary.java index 00ae64b0..9b5c8e57 100644 --- a/src/lombok/transformations/TypeLibrary.java +++ b/src/lombok/transformations/TypeLibrary.java @@ -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; } } diff --git a/src/lombok/transformations/TypeResolver.java b/src/lombok/transformations/TypeResolver.java index 2a012f57..3c3617a6 100644 --- a/src/lombok/transformations/TypeResolver.java +++ b/src/lombok/transformations/TypeResolver.java @@ -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; |