diff options
Diffstat (limited to 'src/lombok')
20 files changed, 1565 insertions, 714 deletions
diff --git a/src/lombok/core/AST.java b/src/lombok/core/AST.java new file mode 100644 index 00000000..99902672 --- /dev/null +++ b/src/lombok/core/AST.java @@ -0,0 +1,268 @@ +package lombok.core; + +import static lombok.Lombok.sneakyThrow; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +public abstract class AST<N> { + public enum Kind { + COMPILATION_UNIT, TYPE, FIELD, INITIALIZER, METHOD, ANNOTATION, ARGUMENT, LOCAL, STATEMENT; + } + + private Node top; + private final String fileName; + private Map<N, Void> identityDetector = new IdentityHashMap<N, Void>(); + private Map<N, Node> nodeMap = new IdentityHashMap<N, Node>(); + + protected AST(String fileName) { + this.fileName = fileName == null ? "(unknown).java" : fileName; + } + + protected void setTop(Node top) { + this.top = top; + } + + public abstract String getPackageDeclaration(); + + public abstract Collection<String> getImportStatements(); + + protected <T extends Node> T putInMap(T parent) { + nodeMap.put(parent.get(), parent); + identityDetector.put(parent.get(), null); + return parent; + } + + protected Map<N, Node> getNodeMap() { + return nodeMap; + } + + protected void clearState() { + identityDetector = new IdentityHashMap<N, Void>(); + nodeMap = new IdentityHashMap<N, Node>(); + } + + protected boolean alreadyHandled(N node) { + return identityDetector.containsKey(node); + } + + protected void setAsHandled(N node) { + identityDetector.put(node, null); + } + + public String getFileName() { + return fileName; + } + + public Node top() { + return top; + } + + public Node get(N node) { + return nodeMap.get(node); + } + + public abstract class Node { + protected final Kind kind; + protected final N node; + protected final Collection<? extends Node> children; + protected Node parent; + protected boolean handled; + protected boolean isStructurallySignificant; + + protected Node(N node, Collection<? extends Node> children, Kind kind) { + this.kind = kind; + this.node = node; + this.children = children == null ? Collections.<Node>emptyList() : children; + for ( Node child : this.children ) child.parent = this; + this.isStructurallySignificant = calculateIsStructurallySignificant(); + } + + public String getPackageDeclaration() { + return AST.this.getPackageDeclaration(); + } + + public Collection<String> getImportStatements() { + return AST.this.getImportStatements(); + } + + protected abstract boolean calculateIsStructurallySignificant(); + + public N get() { + return node; + } + + public Kind getKind() { + return kind; + } + + /** + * Return the name of your type (simple name), method, field, or local variable. Return null if this + * node doesn't really have a name, such as initializers, while statements, etc. + */ + public abstract String getName(); + + /** Returns the structurally significant node that encloses this one. + * + * @see #isStructurallySignificant() + */ + public Node up() { + Node result = (Node)parent; + while ( result != null && !result.isStructurallySignificant ) result = (Node)result.parent; + return result; + } + + /** + * Returns the direct parent node in the AST tree of this node. For example, a local variable declaration's + * direct parent can be e.g. an If block, but its up() Node is the Method that contains it. + */ + public Node directUp() { + return parent; + } + + public Collection<? extends Node> down() { + return children; + } + + public boolean isHandled() { + return handled; + } + + public Node setHandled() { + this.handled = true; + return this; + } + + public Node top() { + return top; + } + + public String getFileName() { + return fileName; + } + + public abstract void addError(String message); + + public abstract void addWarning(String message); + + /** + * Structurally significant means: LocalDeclaration, TypeDeclaration, MethodDeclaration, ConstructorDeclaration, + * FieldDeclaration, Initializer, and CompilationUnitDeclaration. + * The rest is e.g. if statements, while loops, etc. + */ + public boolean isStructurallySignificant() { + return isStructurallySignificant; + } + } + + protected static class FieldAccess { + public final Field field; + public final int dim; + + FieldAccess(Field field, int dim) { + this.field = field; + this.dim = dim; + } + } + + private static Map<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new HashMap<Class<?>, Collection<FieldAccess>>(); + protected Collection<FieldAccess> fieldsOf(Class<?> c) { + Collection<FieldAccess> fields = fieldsOfASTClasses.get(c); + if ( fields != null ) return fields; + + fields = new ArrayList<FieldAccess>(); + getFields(c, fields); + fieldsOfASTClasses.put(c, fields); + return fields; + } + + private void getFields(Class<?> c, Collection<FieldAccess> fields) { + if ( c == Object.class || c == null ) return; + for ( Field f : c.getDeclaredFields() ) { + if ( Modifier.isStatic(f.getModifiers()) ) continue; + Class<?> t = f.getType(); + int dim = 0; + + if ( t.isArray() ) { + while ( t.isArray() ) { + dim++; + t = t.getComponentType(); + } + } else if ( Collection.class.isAssignableFrom(t) ) { + while ( Collection.class.isAssignableFrom(t) ) { + dim++; + t = getComponentType(f.getGenericType()); + } + } + + for ( Class<?> statementType : getStatementTypes() ) { + if ( statementType.isAssignableFrom(t) ) { + f.setAccessible(true); + fields.add(new FieldAccess(f, dim)); + break; + } + } + } + getFields(c.getSuperclass(), fields); + } + + private Class<?> getComponentType(Type type) { + if ( type instanceof ParameterizedType ) { + Type component = ((ParameterizedType)type).getActualTypeArguments()[0]; + return component instanceof Class<?> ? (Class<?>)component : Object.class; + } else return Object.class; + } + + protected abstract Collection<Class<? extends N>> getStatementTypes(); + + protected <T extends Node> Collection<T> buildWithField(Class<T> nodeType, N statement, FieldAccess fa) { + List<T> list = new ArrayList<T>(); + buildWithField0(nodeType, statement, fa, list); + return list; + } + + private <T extends Node> void buildWithField0(Class<T> nodeType, N child, FieldAccess fa, Collection<T> list) { + try { + Object o = fa.field.get(child); + if ( o == null ) return; + if ( fa.dim == 0 ) { + Node node = buildStatement(o); + if ( node != null ) list.add(nodeType.cast(node)); + } else if ( o.getClass().isArray() ) buildWithArray(nodeType, o, list, fa.dim); + else if ( Collection.class.isInstance(o) ) buildWithCollection(nodeType, o, list, fa.dim); + } catch ( IllegalAccessException e ) { + sneakyThrow(e); + } + } + + private <T extends Node> void buildWithArray(Class<T> nodeType, Object array, Collection<T> list, int dim) { + if ( dim == 1 ) for ( Object v : (Object[])array ) { + if ( v == null ) continue; + Node node = buildStatement(v); + if ( node != null ) list.add(nodeType.cast(node)); + } else for ( Object v : (Object[])array ) { + buildWithArray(nodeType, v, list, dim-1); + } + } + + private <T extends Node> void buildWithCollection(Class<T> nodeType, Object collection, Collection<T> list, int dim) { + if ( dim == 1 ) for ( Object v : (Collection<?>)collection ) { + if ( v == null ) continue; + Node node = buildStatement(v); + if ( node != null ) list.add(nodeType.cast(node)); + } else for ( Object v : (Collection<?>)collection ) { + buildWithCollection(nodeType, v, list, dim-1); + } + } + + protected abstract Node buildStatement(Object statement); +} diff --git a/src/lombok/core/AnnotationValues.java b/src/lombok/core/AnnotationValues.java new file mode 100644 index 00000000..ec17e34f --- /dev/null +++ b/src/lombok/core/AnnotationValues.java @@ -0,0 +1,281 @@ +package lombok.core; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AnnotationValues<A extends Annotation> { + private final Class<A> type; + private final Map<String, AnnotationValue> values; + private final AST<?>.Node ast; + + public static class AnnotationValue { + public final List<String> raws; + public final List<Object> valueGuesses; + private final AST<?>.Node node; + + /** + * 'raw' should be the exact expression, for example '5+7', 'AccessLevel.PUBLIC', or 'int.class'. + * 'valueGuess' should be a likely guess at the real value intended. + * + * For classes, supply the class name (qualified or not) as a string.<br /> + * For enums, supply the simple name part (everything after the last dot) as a string.<br /> + */ + public AnnotationValue(AST<?>.Node node, String raw, Object valueGuess) { + this.node = node; + this.raws = Collections.singletonList(raw); + this.valueGuesses = Collections.singletonList(valueGuess); + } + + /** When the value is an array type. */ + public AnnotationValue(AST<?>.Node node, List<String> raws, List<Object> valueGuesses) { + this.node = node; + this.raws = raws; + this.valueGuesses = valueGuesses; + } + + /** + * Override this if you want more specific behaviour (e.g. get the source position just right). + * + * @param message English message with the problem. + * @param valueIdx The index into the values for this annotation key that caused the problem. + * -1 for a problem that applies to all values, otherwise the 0-based index into an array of values. + * If there is no array for this value (e.g. value=1 instead of value={1,2}), then always -1 or 0. + */ + public void setError(String message, int valueIdx) { + node.addError(message); + } + } + + public AnnotationValues(Class<A> type, Map<String, AnnotationValue> values, AST<?>.Node ast) { + this.type = type; + this.values = values; + this.ast = ast; + } + + public static class AnnotationValueDecodeFail extends RuntimeException { + private static final long serialVersionUID = 1L; + + public final int idx; + public final AnnotationValue owner; + + public AnnotationValueDecodeFail(AnnotationValue owner, String msg, int idx) { + super(msg); + this.idx = idx; + this.owner = owner; + } + } + + private static AnnotationValueDecodeFail makeNoDefaultFail(AnnotationValue owner, Method method) { + return new AnnotationValueDecodeFail(owner, + "No value supplied but " + method.getName() + " has no default either.", -1); + } + + @SuppressWarnings("unchecked") + public A getInstance() throws AnnotationValueDecodeFail { + InvocationHandler invocations = new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + AnnotationValue v = values.get(method.getName()); + if ( v == null ) { + Object defaultValue = method.getDefaultValue(); + if ( defaultValue != null ) return defaultValue; + throw makeNoDefaultFail(v, method); + } + + boolean isArray = false; + Class<?> expected = method.getReturnType(); + Object array = null; + if ( expected.isArray() ) { + isArray = true; + expected = expected.getComponentType(); + array = Array.newInstance(expected, 1); + } + + if ( !isArray && v.valueGuesses.size() > 1 ) { + System.out.println(v.valueGuesses.size() + ": " + v.valueGuesses); + throw new AnnotationValueDecodeFail(v, + "Expected a single value, but " + method.getName() + " has an array of values", -1); + } + + if ( v.valueGuesses.size() == 0 && !isArray ) { + Object defaultValue = method.getDefaultValue(); + if ( defaultValue == null ) throw makeNoDefaultFail(v, method); + return defaultValue; + } + + int idx = 0; + for ( Object guess : v.valueGuesses ) { + Object result = guess == null ? null : guessToType(guess, expected, v, idx); + if ( !isArray ) { + if ( result == null ) { + Object defaultValue = method.getDefaultValue(); + if ( defaultValue == null ) throw makeNoDefaultFail(v, method); + return defaultValue; + } else return result; + } else { + if ( result == null ) { + if ( v.valueGuesses.size() == 1 ) { + Object defaultValue = method.getDefaultValue(); + if ( defaultValue == null ) throw makeNoDefaultFail(v, method); + return defaultValue; + } else throw new AnnotationValueDecodeFail(v, + "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); + } + Array.set(array, idx++, result); + } + } + + return array; + } + }; + + return (A) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, invocations); + } + + private Object guessToType(Object guess, Class<?> expected, AnnotationValue v, int pos) { + if ( expected == int.class ) { + if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) { + return ((Number)guess).intValue(); + } + } + + if ( expected == long.class ) { + if ( guess instanceof Long || guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) { + return ((Number)guess).longValue(); + } + } + + if ( expected == short.class ) { + if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) { + int intVal = ((Number)guess).intValue(); + int shortVal = ((Number)guess).shortValue(); + if ( shortVal == intVal ) return shortVal; + } + } + + if ( expected == byte.class ) { + if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) { + int intVal = ((Number)guess).intValue(); + int byteVal = ((Number)guess).byteValue(); + if ( byteVal == intVal ) return byteVal; + } + } + + if ( expected == double.class ) { + if ( guess instanceof Number ) return ((Number)guess).doubleValue(); + } + + if ( expected == float.class ) { + if ( guess instanceof Number ) return ((Number)guess).floatValue(); + } + + if ( expected == boolean.class ) { + if ( guess instanceof Boolean ) return ((Boolean)guess).booleanValue(); + } + + if ( expected == char.class ) { + if ( guess instanceof Character ) return ((Character)guess).charValue(); + } + + if ( expected == String.class ) { + if ( guess instanceof String ) return expected; + } + + if ( Enum.class.isAssignableFrom(expected) ) { + if ( guess instanceof String ) { + for ( Object enumConstant : expected.getEnumConstants() ) { + String target = ((Enum<?>)enumConstant).name(); + if ( target.equals(guess) ) return enumConstant; + } + throw new AnnotationValueDecodeFail(v, + "Can't translate " + guess + " to an enum of type " + expected, pos); + } + } + + if ( Class.class == expected ) { + if ( guess instanceof String ) try { + return Class.forName(toFQ((String)guess)); + } catch ( ClassNotFoundException e ) { + throw new AnnotationValueDecodeFail(v, + "Can't translate " + guess + " to a class object.", pos); + } + } + + throw new AnnotationValueDecodeFail(v, + "Can't translate a " + guess.getClass() + " to the expected " + expected, pos); + } + + public List<String> getRawExpressions(String annotationMethodName) { + AnnotationValue v = values.get(annotationMethodName); + return v == null ? Collections.<String>emptyList() : v.raws; + } + + public String getRawExpression(String annotationMethodName) { + List<String> l = getRawExpressions(annotationMethodName); + return l.isEmpty() ? null : l.get(0); + } + + public List<String> getProbableFQTypes(String annotationMethodName) { + List<String> result = new ArrayList<String>(); + AnnotationValue v = values.get(annotationMethodName); + if ( v == null ) return Collections.emptyList(); + + for ( Object o : v.valueGuesses ) result.add(o == null ? null : toFQ(o.toString())); + return result; + } + + private String toFQ(String typeName) { + Class<?> c; + boolean fqn = typeName.indexOf('.') > -1; + String prefix = fqn ? typeName.substring(0, typeName.indexOf('.')) : typeName; + + for ( String im : ast.getImportStatements() ) { + int idx = im.lastIndexOf('.'); + String simple = im; + if ( idx > -1 ) simple = im.substring(idx+1); + if ( simple.equals(prefix) ) { + return im + typeName.substring(prefix.length()); + } + } + + c = tryClass(typeName); + if ( c != null ) return c.getName(); + + c = tryClass("java.lang." + typeName); + if ( c != null ) return c.getName(); + + //Try star imports + for ( String im : ast.getImportStatements() ) { + if ( im.endsWith(".*") ) { + c = tryClass(im.substring(0, im.length() -1) + typeName); + if ( c != null ) return c.getName(); + } + } + + if ( !fqn ) { + String pkg = ast.getPackageDeclaration(); + if ( pkg != null ) return pkg + "." + typeName; + } + + return null; + } + + private Class<?> tryClass(String name) { + try { + return Class.forName(name); + } catch ( ClassNotFoundException e ) { + return null; + } + } + + public String getProbableFQType(String annotationMethodName) { + List<String> l = getProbableFQTypes(annotationMethodName); + return l.isEmpty() ? null : l.get(0); + } +} diff --git a/src/lombok/eclipse/TypeResolver.java b/src/lombok/core/TypeResolver.java index 41618513..1e356f89 100644 --- a/src/lombok/eclipse/TypeResolver.java +++ b/src/lombok/core/TypeResolver.java @@ -1,44 +1,35 @@ -package lombok.eclipse; +package lombok.core; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import lombok.core.TypeLibrary; -import lombok.eclipse.EclipseAST.Node; - -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 lombok.core.AST.Kind; public class TypeResolver { private final TypeLibrary library; private Collection<String> imports; - - public TypeResolver(TypeLibrary library, EclipseAST.Node top) { + public TypeResolver(TypeLibrary library, String packageString, Collection<String> importStrings) { this.library = library; - this.imports = makeImportList((CompilationUnitDeclaration) top.getEclipseNode()); + this.imports = makeImportList(packageString, importStrings); } - private static Collection<String> makeImportList(CompilationUnitDeclaration declaration) { + private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) { Set<String> imports = new HashSet<String>(); - if ( declaration.currentPackage != null ) imports.add(toQualifiedName(declaration.currentPackage.getImportName()) + ".*"); - if ( declaration.imports != null ) for ( ImportReference importStatement : declaration.imports ) { - imports.add(toQualifiedName(importStatement.getImportName())); - } + if ( packageString != null ) imports.add(packageString + ".*"); + imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings); return imports; } - public Collection<String> findTypeMatches(Node context, TypeReference type) { - Collection<String> potentialMatches = library.findCompatible(toQualifiedName(type.getTypeName())); + public Collection<String> findTypeMatches(AST<?>.Node context, String typeRef) { + Collection<String> potentialMatches = library.findCompatible(typeRef); if ( potentialMatches.isEmpty() ) return Collections.emptyList(); - if ( type.getTypeName().length > 1 ) return potentialMatches; - - String simpleName = new String(type.getTypeName()[0]); + int idx = typeRef.indexOf('.'); + if ( idx > -1 ) return potentialMatches; + String simpleName = typeRef.substring(idx+1); //If there's an import statement that explicitly imports a 'Getter' that isn't any of our potentials, return no matches. if ( nameConflictInImportList(simpleName, potentialMatches) ) return Collections.emptyList(); @@ -48,11 +39,11 @@ 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; + AST<?>.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(); + if ( n.getKind() == Kind.TYPE ) { + String name = n.getName(); + if ( name != null && name.equals(simpleName) ) return Collections.emptyList(); } n = n.up(); } @@ -65,7 +56,7 @@ public class TypeResolver { Set<String> results = new HashSet<String>(); for ( String importedType : imports ) { - Collection<String> reduced = library.findCompatible(importedType); + Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType)); reduced.retainAll(potentialMatches); results.addAll(reduced); } @@ -87,14 +78,4 @@ public class TypeResolver { int idx = typeName.lastIndexOf('.'); return idx == -1 ? typeName : typeName.substring(idx+1); } - - private static String toQualifiedName(char[][] typeName) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for ( char[] c : typeName ) { - sb.append(first ? "" : ".").append(c); - first = false; - } - return sb.toString(); - } } diff --git a/src/lombok/eclipse/Eclipse.java b/src/lombok/eclipse/Eclipse.java index e1ff95cd..961b9536 100644 --- a/src/lombok/eclipse/Eclipse.java +++ b/src/lombok/eclipse/Eclipse.java @@ -31,4 +31,14 @@ public class Eclipse { log.log(new Status(IStatus.ERROR, bundleName, message, error)); } + + static String toQualifiedName(char[][] typeName) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for ( char[] c : typeName ) { + sb.append(first ? "" : ".").append(c); + first = false; + } + return sb.toString(); + } } diff --git a/src/lombok/eclipse/EclipseAST.java b/src/lombok/eclipse/EclipseAST.java index 3fdd02e4..53a993ec 100644 --- a/src/lombok/eclipse/EclipseAST.java +++ b/src/lombok/eclipse/EclipseAST.java @@ -1,17 +1,13 @@ package lombok.eclipse; -import static lombok.Lombok.sneakyThrow; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import lombok.core.AST; + import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.apt.dispatch.AptProblem; @@ -22,24 +18,44 @@ import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.Clinit; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; import org.eclipse.jdt.internal.compiler.ast.Initializer; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.util.Util; -public class EclipseAST { +public class EclipseAST extends AST<ASTNode> { + @Override public String getPackageDeclaration() { + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + ImportReference pkg = cud.currentPackage; + return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); + } + + @Override public Collection<String> getImportStatements() { + List<String> imports = new ArrayList<String>(); + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + if ( cud.imports == null ) return imports; + for ( ImportReference imp : cud.imports ) { + if ( imp == null ) continue; + imports.add(Eclipse.toQualifiedName(imp.getImportName())); + } + + return imports; + } + public void traverse(EclipseASTVisitor visitor) { Node current = top(); - visitor.visitCompilationUnit(current, (CompilationUnitDeclaration)current.node); + visitor.visitCompilationUnit(current, (CompilationUnitDeclaration)current.get()); traverseChildren(visitor, current); - visitor.endVisitCompilationUnit(current, (CompilationUnitDeclaration)current.node); + visitor.endVisitCompilationUnit(current, (CompilationUnitDeclaration)current.get()); } private void traverseChildren(EclipseASTVisitor visitor, Node node) { - for ( Node child : node.children ) { - ASTNode n = child.node; + for ( Node child : node.down() ) { + ASTNode n = child.get(); if ( n instanceof TypeDeclaration ) { visitor.visitType(child, (TypeDeclaration)n); traverseChildren(visitor, child); @@ -57,20 +73,33 @@ public class EclipseAST { visitor.visitMethod(child, (AbstractMethodDeclaration)n); traverseChildren(visitor, child); visitor.endVisitMethod(child, (AbstractMethodDeclaration)n); + } else if ( n instanceof Argument ) { + ASTNode parent = child.up().get(); + AbstractMethodDeclaration method = null; + if ( parent instanceof AbstractMethodDeclaration ) method = (AbstractMethodDeclaration)parent; + else System.out.println("Weird, this isn't a desc of method: " + parent.getClass() + ": " + parent); + visitor.visitMethodArgument(child, (Argument)n, method); + traverseChildren(visitor, child); + visitor.endVisitMethodArgument(child, (Argument)n, method); } else if ( n instanceof LocalDeclaration ) { visitor.visitLocal(child, (LocalDeclaration)n); traverseChildren(visitor, child); visitor.endVisitLocal(child, (LocalDeclaration)n); } else if ( n instanceof Annotation ) { Node parent = child.up(); - if ( parent.node instanceof TypeDeclaration ) - visitor.visitAnnotationOnType((TypeDeclaration)parent.node, child, (Annotation)n); - else if ( parent.node instanceof AbstractMethodDeclaration ) - visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)parent.node, child, (Annotation)n); - else if ( parent.node instanceof FieldDeclaration ) - visitor.visitAnnotationOnField((FieldDeclaration)parent.node, child, (Annotation)n); - else if ( parent.node instanceof LocalDeclaration ) - visitor.visitAnnotationOnLocal((LocalDeclaration)parent.node, child, (Annotation)n); + if ( parent.get() instanceof TypeDeclaration ) + visitor.visitAnnotationOnType((TypeDeclaration)parent.get(), child, (Annotation)n); + else if ( parent.get() instanceof AbstractMethodDeclaration ) + visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)parent.get(), child, (Annotation)n); + else if ( parent.get() instanceof FieldDeclaration ) + visitor.visitAnnotationOnField((FieldDeclaration)parent.get(), child, (Annotation)n); + else if ( parent.get() instanceof Argument ) + visitor.visitAnnotationOnMethodArgument( + (Argument)parent.get(), + (AbstractMethodDeclaration)parent.directUp().get(), + child, (Annotation)n); + else if ( parent.get() instanceof LocalDeclaration ) + visitor.visitAnnotationOnLocal((LocalDeclaration)parent.get(), child, (Annotation)n); } else if ( n instanceof Statement ) { visitor.visitStatement(child, (Statement)n); traverseChildren(visitor, child); @@ -83,16 +112,12 @@ public class EclipseAST { return completeParse; } - public String getFileName() { - return fileName; - } - - public Node top() { - return top; + @Override public Node top() { + return (Node) super.top(); } public Node get(ASTNode node) { - return nodeMap.get(node); + return (Node) super.get(node); } private class ParseProblem { @@ -111,14 +136,14 @@ public class EclipseAST { } void addToCompilationResult() { - addProblemToCompilationResult(getFileName(), (CompilationUnitDeclaration) top().getEclipseNode(), - isWarning, message, node.getEclipseNode(), sourceStart, sourceEnd); + addProblemToCompilationResult(getFileName(), (CompilationUnitDeclaration) top().get(), + isWarning, message, node.get(), sourceStart, sourceEnd); } } - public void propagateProblems() { + private void propagateProblems() { if ( queuedProblems.isEmpty() ) return; - CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().getEclipseNode(); + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); if ( cud.compilationResult == null ) return; for ( ParseProblem problem : queuedProblems ) problem.addToCompilationResult(); queuedProblems.clear(); @@ -152,59 +177,44 @@ public class EclipseAST { ast.compilationResult.record(ecProblem, null); } - public final class Node { - final ASTNode node; - Node parent; - final Collection<Node> children; - boolean handled; - private final boolean isStructurallySignificant; + public final class Node extends AST<ASTNode>.Node { + Node(ASTNode node, Collection<Node> children, Kind kind) { + super(node, children, kind); + } - Node(ASTNode node, Collection<Node> children) { - this.node = node; - this.children = children == null ? Collections.<Node>emptyList() : children; - this.isStructurallySignificant = calculateIsStructurallySignificant(); + @Override public String getName() { + final char[] n; + if ( node instanceof TypeDeclaration ) n = ((TypeDeclaration)node).name; + else if ( node instanceof FieldDeclaration ) n = ((FieldDeclaration)node).name; + else if ( node instanceof AbstractMethodDeclaration ) n = ((AbstractMethodDeclaration)node).selector; + else if ( node instanceof LocalDeclaration ) n = ((LocalDeclaration)node).name; + else n = null; + + return n == null ? null : new String(n); } - public void addError(String message) { - this.addError(message, this.getEclipseNode().sourceStart, this.getEclipseNode().sourceEnd); + @Override public void addError(String message) { + this.addError(message, this.get().sourceStart, this.get().sourceEnd); } public void addError(String message, int sourceStart, int sourceEnd) { addProblem(new ParseProblem(false, message, this, sourceStart, sourceEnd)); } - public void addWarning(String message) { - this.addWarning(message, this.getEclipseNode().sourceStart, this.getEclipseNode().sourceEnd); + @Override public void addWarning(String message) { + this.addWarning(message, this.get().sourceStart, this.get().sourceEnd); } public void addWarning(String message, int sourceStart, int sourceEnd) { addProblem(new ParseProblem(true, message, this, sourceStart, sourceEnd)); } - public ASTNode getEclipseNode() { - return node; - } - - /** Returns the structurally significant node that encloses this one. - * - * @see #isStructurallySignificant() - */ - public Node up() { - Node result = parent; - while ( result != null && !result.isStructurallySignificant() ) result = result.parent; - return result; - } - - /** - * Structurally significant means: LocalDeclaration, TypeDeclaration, MethodDeclaration, ConstructorDeclaration, - * FieldDeclaration, Initializer, and CompilationUnitDeclaration. - * The rest is e.g. if statements, while loops, etc. - */ - public boolean isStructurallySignificant() { - return isStructurallySignificant; + /** {@inheritDoc} */ + @Override public Node up() { + return (Node) super.up(); } - private boolean calculateIsStructurallySignificant() { + @Override protected boolean calculateIsStructurallySignificant() { if ( node instanceof TypeDeclaration ) return true; if ( node instanceof AbstractMethodDeclaration ) return true; if ( node instanceof FieldDeclaration ) return true; @@ -213,33 +223,20 @@ public class EclipseAST { return false; } - /** - * Returns the direct parent node in the AST tree of this node. For example, a local variable declaration's - * direct parent can be e.g. an If block, but its up() Node is the Method that contains it. - */ + /** {@inheritDoc} */ public Node directUp() { - return parent; - } - - public Collection<Node> down() { - return children; - } - - public boolean isHandled() { - return handled; + return (Node) super.directUp(); } - public Node setHandled() { - this.handled = true; - return this; + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public Collection<Node> down() { + return (Collection<Node>) children; } - public Node top() { - return top; - } - - public String getFileName() { - return fileName; + /** {@inheritDoc} */ + @Override public Node top() { + return (Node) super.top(); } public boolean isCompleteParse() { @@ -247,33 +244,33 @@ public class EclipseAST { } } - private final Map<ASTNode, Void> identityDetector = new IdentityHashMap<ASTNode, Void>(); - private Map<ASTNode, Node> nodeMap = new HashMap<ASTNode, Node>(); private final CompilationUnitDeclaration compilationUnitDeclaration; - private final String fileName; - private Node top; private boolean completeParse; public EclipseAST(CompilationUnitDeclaration ast) { + super(toFileName(ast)); this.compilationUnitDeclaration = ast; - this.fileName = ast.compilationResult.fileName == null ? "(unknown).java" : new String(ast.compilationResult.fileName); - this.top = buildTree(ast); + setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); } + private static String toFileName(CompilationUnitDeclaration ast) { + return ast.compilationResult.fileName == null ? null : new String(ast.compilationResult.fileName); + } + public void reparse() { propagateProblems(); if ( completeParse ) return; boolean newCompleteParse = isComplete(compilationUnitDeclaration); if ( !newCompleteParse ) return; - Map<ASTNode, Node> oldMap = nodeMap; - nodeMap = new HashMap<ASTNode, Node>(); - this.top = buildTree(compilationUnitDeclaration); + Map<ASTNode, AST<ASTNode>.Node> oldMap = getNodeMap(); + clearState(); + setTop(buildCompilationUnit(compilationUnitDeclaration)); //Retain 'handled' flags. - for ( Map.Entry<ASTNode, Node> e : nodeMap.entrySet() ) { - Node oldEntry = oldMap.get(e.getKey()); - if ( oldEntry != null && oldEntry.handled ) e.getValue().handled = true; + for ( Map.Entry<ASTNode, AST<ASTNode>.Node> e : getNodeMap().entrySet() ) { + Node oldEntry = (Node) oldMap.get(e.getKey()); + if ( oldEntry != null && oldEntry.isHandled() ) e.getValue().setHandled(); } this.completeParse = true; @@ -283,44 +280,36 @@ public class EclipseAST { return (unit.bits & ASTNode.HasAllMethodBodies) > 0; } - private Node putInMap(Node parent) { - for ( Node child : parent.children ) child.parent = parent; - nodeMap.put(parent.node, parent); - identityDetector.put(parent.node, null); - return parent; - } - - private Node buildTree(CompilationUnitDeclaration top) { - identityDetector.clear(); - Collection<Node> children = buildTree(top.types); - return putInMap(new Node(top, children)); + private Node buildCompilationUnit(CompilationUnitDeclaration top) { + Collection<Node> children = buildTypes(top.types); + return putInMap(new Node(top, children, Kind.COMPILATION_UNIT)); } private void addIfNotNull(Collection<Node> collection, Node n) { if ( n != null ) collection.add(n); } - private Collection<Node> buildTree(TypeDeclaration[] children) { + private Collection<Node> buildTypes(TypeDeclaration[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); - for ( TypeDeclaration type : children ) addIfNotNull(childNodes, buildTree(type)); + for ( TypeDeclaration type : children ) addIfNotNull(childNodes, buildType(type)); return childNodes; } - private Node buildTree(TypeDeclaration type) { - if ( identityDetector.containsKey(type) ) return null; + private Node buildType(TypeDeclaration type) { + if ( alreadyHandled(type) ) return null; List<Node> childNodes = new ArrayList<Node>(); - childNodes.addAll(buildTree(type.fields)); - childNodes.addAll(buildTree(type.memberTypes)); - childNodes.addAll(buildTree(type.methods)); - childNodes.addAll(buildTree(type.annotations)); - return putInMap(new Node(type, childNodes)); + childNodes.addAll(buildFields(type.fields)); + childNodes.addAll(buildTypes(type.memberTypes)); + childNodes.addAll(buildMethods(type.methods)); + childNodes.addAll(buildAnnotations(type.annotations)); + return putInMap(new Node(type, childNodes, Kind.TYPE)); } - private Collection<Node> buildTree(FieldDeclaration[] children) { + private Collection<Node> buildFields(FieldDeclaration[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); - for ( FieldDeclaration child : children ) addIfNotNull(childNodes, buildTree(child)); + for ( FieldDeclaration child : children ) addIfNotNull(childNodes, buildField(child)); return childNodes; } @@ -329,152 +318,97 @@ public class EclipseAST { else return Collections.singleton(item); } - private Node buildTree(FieldDeclaration field) { - if ( field instanceof Initializer ) return buildTree((Initializer)field); - if ( identityDetector.containsKey(field) ) return null; + private Node buildField(FieldDeclaration field) { + if ( field instanceof Initializer ) return buildInitializer((Initializer)field); + if ( alreadyHandled(field) ) return null; List<Node> childNodes = new ArrayList<Node>(); - addIfNotNull(childNodes, buildWithStatement(field.initialization)); - childNodes.addAll(buildTree(field.annotations)); - return putInMap(new Node(field, childNodes)); + addIfNotNull(childNodes, buildStatement(field.initialization)); + childNodes.addAll(buildAnnotations(field.annotations)); + return putInMap(new Node(field, childNodes, Kind.FIELD)); } - private Node buildTree(Initializer initializer) { - if ( identityDetector.containsKey(initializer) ) return null; - return putInMap(new Node(initializer, singleton(buildWithStatement(initializer.block)))); + private Node buildInitializer(Initializer initializer) { + if ( alreadyHandled(initializer) ) return null; + return putInMap(new Node(initializer, singleton(buildStatement(initializer.block)), Kind.INITIALIZER)); } - private Collection<Node> buildTree(AbstractMethodDeclaration[] children) { + private Collection<Node> buildMethods(AbstractMethodDeclaration[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); - for (AbstractMethodDeclaration method : children ) addIfNotNull(childNodes, buildTree(method)); + for (AbstractMethodDeclaration method : children ) addIfNotNull(childNodes, buildMethod(method)); return childNodes; } - private Node buildTree(AbstractMethodDeclaration method) { - if ( identityDetector.containsKey(method) ) return null; + private Node buildMethod(AbstractMethodDeclaration method) { + if ( alreadyHandled(method) ) return null; List<Node> childNodes = new ArrayList<Node>(); - childNodes.addAll(buildTree(method.arguments)); - childNodes.addAll(buildTree(method.statements)); - childNodes.addAll(buildTree(method.annotations)); - return putInMap(new Node(method, childNodes)); + childNodes.addAll(buildArguments(method.arguments)); + childNodes.addAll(buildStatements(method.statements)); + childNodes.addAll(buildAnnotations(method.annotations)); + return putInMap(new Node(method, childNodes, Kind.METHOD)); } //Arguments are a kind of LocalDeclaration. They can definitely contain lombok annotations, so we care about them. - private Collection<Node> buildTree(Argument[] children) { + private Collection<Node> buildArguments(Argument[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); for ( LocalDeclaration local : children ) { - addIfNotNull(childNodes, buildTree(local)); + addIfNotNull(childNodes, buildLocal(local)); } return childNodes; } - private Node buildTree(LocalDeclaration local) { - if ( identityDetector.containsKey(local) ) return null; + private Node buildLocal(LocalDeclaration local) { + if ( alreadyHandled(local) ) return null; List<Node> childNodes = new ArrayList<Node>(); - addIfNotNull(childNodes, buildWithStatement(local.initialization)); - childNodes.addAll(buildTree(local.annotations)); - return putInMap(new Node(local, childNodes)); + addIfNotNull(childNodes, buildStatement(local.initialization)); + childNodes.addAll(buildAnnotations(local.annotations)); + return putInMap(new Node(local, childNodes, Kind.LOCAL)); } - private Collection<Node> buildTree(Annotation[] annotations) { + private Collection<Node> buildAnnotations(Annotation[] annotations) { if ( annotations == null ) return Collections.emptyList(); List<Node> elements = new ArrayList<Node>(); for ( Annotation an : annotations ) { if ( an == null ) continue; - elements.add(putInMap(new Node(an, null))); + if ( alreadyHandled(an) ) continue; + elements.add(putInMap(new Node(an, null, Kind.ANNOTATION))); } return elements; } - private Collection<Node> buildTree(Statement[] children) { + private Collection<Node> buildStatements(Statement[] children) { if ( children == null ) return Collections.emptyList(); List<Node> childNodes = new ArrayList<Node>(); - for ( Statement child : children ) addIfNotNull(childNodes, buildWithStatement(child)); + for ( Statement child : children ) addIfNotNull(childNodes, buildStatement(child)); return childNodes; } //Almost anything is a statement, so this method has a different name to avoid overloading confusion - private Node buildWithStatement(Statement child) { - if ( child == null || identityDetector.containsKey(child) ) return null; - if ( child instanceof TypeDeclaration ) return buildTree((TypeDeclaration)child); + private Node buildStatement(Statement child) { + if ( child == null || alreadyHandled(child) ) return null; + if ( child instanceof TypeDeclaration ) return buildType((TypeDeclaration)child); - if ( child instanceof LocalDeclaration ) return buildTree((LocalDeclaration)child); + if ( child instanceof LocalDeclaration ) return buildLocal((LocalDeclaration)child); //We drill down because LocalDeclarations and TypeDeclarations can occur anywhere, even in, say, //an if block, or even the expression on an assert statement! - identityDetector.put(child, null); + setAsHandled(child); return drill(child); } - private Node drill(Statement statement) { + protected Node drill(Statement statement) { List<Node> childNodes = new ArrayList<Node>(); - for ( FieldAccess fa : fieldsOf(statement.getClass()) ) childNodes.addAll(buildWithField(statement, fa)); - return putInMap(new Node(statement, childNodes)); - } - - private static class FieldAccess { - final Field field; - final int dim; - - FieldAccess(Field field, int dim) { - this.field = field; - this.dim = dim; - } - } - - private static Map<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new HashMap<Class<?>, Collection<FieldAccess>>(); - private Collection<FieldAccess> fieldsOf(Class<?> c) { - Collection<FieldAccess> fields = fieldsOfASTClasses.get(c); - if ( fields != null ) return fields; - - fields = new ArrayList<FieldAccess>(); - getFields(c, fields); - fieldsOfASTClasses.put(c, fields); - return fields; - } - - private void getFields(Class<?> c, Collection<FieldAccess> fields) { - if ( c == ASTNode.class || c == null ) return; - for ( Field f : c.getDeclaredFields() ) { - if ( Modifier.isStatic(f.getModifiers()) ) continue; - Class<?> t = f.getType(); - int dim = 0; - while ( t.isArray() ) { - dim++; - t = t.getComponentType(); - } - if ( Statement.class.isAssignableFrom(t) ) { - f.setAccessible(true); - fields.add(new FieldAccess(f, dim)); - } - } - getFields(c.getSuperclass(), fields); + for ( FieldAccess fa : fieldsOf(statement.getClass()) ) childNodes.addAll(buildWithField(Node.class, statement, fa)); + return putInMap(new Node(statement, childNodes, Kind.STATEMENT)); } - private Collection<Node> buildWithField(Statement statement, FieldAccess fa) { - List<Node> list = new ArrayList<Node>(); - buildWithField(statement, fa, list); - return list; + @Override protected Collection<Class<? extends ASTNode>> getStatementTypes() { + return Collections.<Class<? extends ASTNode>>singleton(Statement.class); } - private void buildWithField(Statement child, FieldAccess fa, Collection<Node> list) { - try { - Object o = fa.field.get(child); - if ( fa.dim == 0 ) addIfNotNull(list, buildWithStatement((Statement)o)); - else buildWithArray(o, list, fa.dim); - } catch ( IllegalAccessException e ) { - sneakyThrow(e); - } - } - - private void buildWithArray(Object array, Collection<Node> list, int dim) { - if ( array == null ) return; - if ( dim == 1 ) for ( Object v : (Object[])array ) { - addIfNotNull(list, buildWithStatement((Statement)v)); - } else for ( Object v : (Object[])array ) { - buildWithArray(v, list, dim-1); - } + @Override protected Node buildStatement(Object node) { + return buildStatement((Statement)node); } } diff --git a/src/lombok/eclipse/EclipseASTAdapter.java b/src/lombok/eclipse/EclipseASTAdapter.java index 1bd5ae3d..61d161d1 100644 --- a/src/lombok/eclipse/EclipseASTAdapter.java +++ b/src/lombok/eclipse/EclipseASTAdapter.java @@ -4,6 +4,7 @@ import lombok.eclipse.EclipseAST.Node; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.Initializer; @@ -25,6 +26,9 @@ public abstract class EclipseASTAdapter implements EclipseASTVisitor { @Override public void visitMethod(Node methodNode, AbstractMethodDeclaration method) {} @Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, Node annotationNode, Annotation annotation) {} @Override public void endVisitMethod(Node methodNode, AbstractMethodDeclaration method) {} + @Override public void visitMethodArgument(Node argNode, Argument arg, AbstractMethodDeclaration method) {} + @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, Node annotationNode, Annotation annotation) {} + @Override public void endVisitMethodArgument(Node argNode, Argument arg, AbstractMethodDeclaration method) {} @Override public void visitLocal(Node localNode, LocalDeclaration local) {} @Override public void visitAnnotationOnLocal(LocalDeclaration local, Node annotationNode, Annotation annotation) {} @Override public void endVisitLocal(Node localNode, LocalDeclaration local) {} diff --git a/src/lombok/eclipse/EclipseASTVisitor.java b/src/lombok/eclipse/EclipseASTVisitor.java index 9ab56ebe..0c55d222 100644 --- a/src/lombok/eclipse/EclipseASTVisitor.java +++ b/src/lombok/eclipse/EclipseASTVisitor.java @@ -60,8 +60,14 @@ public interface EclipseASTVisitor { void endVisitMethod(Node methodNode, AbstractMethodDeclaration method); /** - * Visits a local declaration - that is, something like 'int x = 10;' on the method level. Also called - * for method parameter (those would be Arguments, a subclass of LocalDeclaration). + * Visits a method argument + */ + void visitMethodArgument(Node argNode, Argument arg, AbstractMethodDeclaration method); + void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, Node annotationNode, Annotation annotation); + void endVisitMethodArgument(Node argNode, Argument arg, AbstractMethodDeclaration method); + + /** + * Visits a local declaration - that is, something like 'int x = 10;' on the method level. */ void visitLocal(Node localNode, LocalDeclaration local); void visitAnnotationOnLocal(LocalDeclaration local, Node annotationNode, Annotation annotation); @@ -69,8 +75,6 @@ public interface EclipseASTVisitor { /** * Visits a statement that isn't any of the other visit methods (e.g. TypeDeclaration). - * @param node - * @param statement */ void visitStatement(Node statementNode, Statement statement); void endVisitStatement(Node statementNode, Statement statement); @@ -171,9 +175,22 @@ public interface EclipseASTVisitor { print("</%s %s>", type, str(method.selector)); } + @Override public void visitMethodArgument(Node node, Argument arg, AbstractMethodDeclaration method) { + print("<METHODARG %s %s = %s>", str(arg.type), str(arg.name), arg.initialization); + indent++; + } + + @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, Node node, Annotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitMethodArgument(Node node, Argument arg, AbstractMethodDeclaration method) { + indent--; + print("</METHODARG %s %s>", str(arg.type), str(arg.name)); + } + @Override public void visitLocal(Node node, LocalDeclaration local) { - String type = local instanceof Argument ? "ARGUMENT" : "LOCAL"; - print("<%s %s %s = %s>", type, str(local.type), str(local.name), local.initialization); + print("<LOCAL %s %s = %s>", str(local.type), str(local.name), local.initialization); indent++; } @@ -182,9 +199,8 @@ public interface EclipseASTVisitor { } @Override public void endVisitLocal(Node node, LocalDeclaration local) { - String type = local instanceof Argument ? "ARGUMENT" : "LOCAL"; indent--; - print("</%s %s %s>", type, str(local.type), str(local.name)); + print("</LOCAL %s %s>", str(local.type), str(local.name)); } @Override public void visitStatement(Node node, Statement statement) { diff --git a/src/lombok/eclipse/EclipseAnnotationHandler.java b/src/lombok/eclipse/EclipseAnnotationHandler.java index c6cc23be..816ba61c 100644 --- a/src/lombok/eclipse/EclipseAnnotationHandler.java +++ b/src/lombok/eclipse/EclipseAnnotationHandler.java @@ -1,5 +1,7 @@ package lombok.eclipse; +import lombok.core.AnnotationValues; + public interface EclipseAnnotationHandler<T extends java.lang.annotation.Annotation> { - void handle(T annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseAST.Node annotationNode); + void handle(AnnotationValues<T> annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseAST.Node annotationNode); } diff --git a/src/lombok/eclipse/HandlerLibrary.java b/src/lombok/eclipse/HandlerLibrary.java index 8a7f6edc..2e3e4541 100644 --- a/src/lombok/eclipse/HandlerLibrary.java +++ b/src/lombok/eclipse/HandlerLibrary.java @@ -1,34 +1,36 @@ package lombok.eclipse; +import static lombok.eclipse.Eclipse.toQualifiedName; + import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; +import lombok.core.AnnotationValues; import lombok.core.SpiLoadUtil; import lombok.core.TypeLibrary; +import lombok.core.TypeResolver; +import lombok.core.AnnotationValues.AnnotationValue; +import lombok.core.AnnotationValues.AnnotationValueDecodeFail; import lombok.eclipse.EclipseAST.Node; 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 { @@ -43,11 +45,56 @@ public class HandlerLibrary { this.annotationClass = annotationClass; } - @SuppressWarnings("unchecked") - public void handle(Object annInstance, - org.eclipse.jdt.internal.compiler.ast.Annotation annotation, - Node annotationNode) { - handler.handle((T) annInstance, annotation, annotationNode); + public void handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation, + final Node annotationNode) { + Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); + + final MemberValuePair[] pairs = annotation.memberValuePairs(); + for ( Method m : annotationClass.getDeclaredMethods() ) { + if ( !Modifier.isPublic(m.getModifiers()) ) continue; + String name = m.getName(); + List<String> raws = new ArrayList<String>(); + List<Object> guesses = new ArrayList<Object>(); + Expression fullExpression = null; + Expression[] expressions = null; + + if ( pairs != null ) for ( MemberValuePair pair : pairs ) { + char[] n = pair.name; + String mName = n == null ? "value" : new String(name); + if ( !mName.equals(name) ) continue; + fullExpression = pair.value; + } + + if ( fullExpression != null ) { + if ( fullExpression instanceof ArrayInitializer ) { + expressions = ((ArrayInitializer)fullExpression).expressions; + } else expressions = new Expression[] { fullExpression }; + for ( Expression ex : expressions ) { + StringBuffer sb = new StringBuffer(); + ex.print(0, sb); + raws.add(sb.toString()); + guesses.add(calculateValue(ex)); + } + } + + final Expression fullExpr = fullExpression; + final Expression[] exprs = expressions; + + values.put(name, new AnnotationValue(annotationNode, raws, guesses) { + @Override public void setError(String message, int valueIdx) { + Expression ex; + if ( valueIdx == -1 ) ex = fullExpr; + else ex = exprs[valueIdx]; + + int sourceStart = ex.sourceStart; + int sourceEnd = ex.sourceEnd; + + annotationNode.addError(message, sourceStart, sourceEnd); + } + }); + } + + handler.handle(new AnnotationValues<T>(annotationClass, values, annotationNode), annotation, annotationNode); } } @@ -56,192 +103,32 @@ public class HandlerLibrary { private Collection<EclipseASTVisitor> visitorHandlers = new ArrayList<EclipseASTVisitor>(); - @SuppressWarnings("unchecked") - public <A extends Annotation> A createAnnotation(Class<A> target, - CompilationUnitDeclaration ast, - org.eclipse.jdt.internal.compiler.ast.Annotation node) throws AnnotationValueDecodeFail { - 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(pair, 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(MemberValuePair pair, - CompilationUnitDeclaration ast, Class<?> type, Expression e) throws AnnotationValueDecodeFail { + private static Object calculateValue(Expression e) { if ( e instanceof Literal ) { ((Literal)e).computeConstant(); - return convertConstant(pair, type, e.constant); - } else if ( e instanceof ArrayInitializer ) { - if ( !type.isArray() ) throw new AnnotationValueDecodeFail(pair, "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(pair, ast, component, expressions[i]); + switch ( e.constant.typeID() ) { + case TypeIds.T_int: return e.constant.intValue(); + case TypeIds.T_byte: return e.constant.byteValue(); + case TypeIds.T_short: return e.constant.shortValue(); + case TypeIds.T_char: return e.constant.charValue(); + case TypeIds.T_float: return e.constant.floatValue(); + case TypeIds.T_double: return e.constant.doubleValue(); + case TypeIds.T_boolean: return e.constant.booleanValue(); + case TypeIds.T_long: return e.constant.longValue(); + case TypeIds.T_JavaLangString: return e.constant.stringValue(); + default: return null; } - return values; } else if ( e instanceof ClassLiteralAccess ) { - if ( type == Class.class ) return toClass(pair, ast, str(((ClassLiteralAccess)e).type.getTypeName())); - else throw new AnnotationValueDecodeFail(pair, "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) ) return toEnum(pair, type, s); - throw new AnnotationValueDecodeFail(pair, "Lombok annotations must contain literals only."); - } else { - throw new AnnotationValueDecodeFail(pair, "Lombok could not decode this annotation parameter."); - } - } - - private Enum<?> toEnum(MemberValuePair pair, Class<?> enumType, String ref) throws AnnotationValueDecodeFail { - 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; + return Eclipse.toQualifiedName(((ClassLiteralAccess)e).type.getTypeName()); + } else if ( e instanceof SingleNameReference ) { + return new String(((SingleNameReference)e).token); + } else if ( e instanceof QualifiedNameReference ) { + String qName = Eclipse.toQualifiedName(((QualifiedNameReference)e).tokens); + int idx = qName.lastIndexOf('.'); + return idx == -1 ? qName : qName.substring(idx+1); } - throw new AnnotationValueDecodeFail(pair, "I can't figure out which enum constant you mean."); - } - - private Class<?> toClass(MemberValuePair pair, CompilationUnitDeclaration ast, String typeName) throws AnnotationValueDecodeFail { - 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; - if ( !fqn ) { - c = tryClass(typeName); - if ( c != null ) return c; - } - - //Try star imports - for ( ImportReference ref : ast.imports ) { - String im = str(ref.tokens); - if ( im.endsWith(".*") ) { - c = tryClass(im.substring(0, im.length() -1) + typeName); - if ( c != null ) return c; - } - } - - throw new AnnotationValueDecodeFail(pair, "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(MemberValuePair pair, Class<?> type, Constant constant) throws AnnotationValueDecodeFail { - 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 AnnotationValueDecodeFail(pair, "Expected a constant of some sort here (a number or a string)"); - } - if ( !Expression.isConstantValueRepresentable(constant, constant.typeID(), targetTypeID) ) { - throw new AnnotationValueDecodeFail(pair, "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 AnnotationValueDecodeFail extends Exception { - private static final long serialVersionUID = 1L; - - MemberValuePair pair; - - AnnotationValueDecodeFail(MemberValuePair pair, String msg) { - super(msg); - this.pair = pair; - } - } - - 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(); + return null; } public static HandlerLibrary load() { @@ -284,22 +171,20 @@ public class HandlerLibrary { public void handle(CompilationUnitDeclaration ast, EclipseAST.Node annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation) { - TypeResolver resolver = new TypeResolver(typeLibrary, annotationNode.top()); + String pkgName = annotationNode.getPackageDeclaration(); + Collection<String> imports = annotationNode.getImportStatements(); + + TypeResolver resolver = new TypeResolver(typeLibrary, pkgName, imports); TypeReference rawType = annotation.type; if ( rawType == null ) return; - for ( String fqn : resolver.findTypeMatches(annotationNode, annotation.type) ) { + for ( String fqn : resolver.findTypeMatches(annotationNode, toQualifiedName(annotation.type.getTypeName())) ) { AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); if ( container == null ) continue; - Object annInstance; - try { - annInstance = createAnnotation(container.annotationClass, ast, annotation); - } catch ( AnnotationValueDecodeFail e ) { - annotationNode.addError(e.getMessage(), e.pair.sourceStart, e.pair.sourceEnd); - return; - } try { - container.handle(annInstance, annotation, annotationNode); + container.handle(annotation, annotationNode); + } catch ( AnnotationValueDecodeFail fail ) { + fail.owner.setError(fail.getMessage(), fail.idx); } catch ( Throwable t ) { Eclipse.error(String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); } diff --git a/src/lombok/eclipse/TransformEclipseAST.java b/src/lombok/eclipse/TransformEclipseAST.java index 55cdd822..f77639e1 100644 --- a/src/lombok/eclipse/TransformEclipseAST.java +++ b/src/lombok/eclipse/TransformEclipseAST.java @@ -6,6 +6,7 @@ import lombok.eclipse.EclipseAST.Node; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; @@ -114,25 +115,31 @@ public class TransformEclipseAST { private static class AnnotationVisitor extends EclipseASTAdapter { @Override public void visitAnnotationOnField(FieldDeclaration field, Node annotationNode, Annotation annotation) { if ( annotationNode.isHandled() ) return; - handlers.handle((CompilationUnitDeclaration) annotationNode.top().node, annotationNode, annotation); + handlers.handle((CompilationUnitDeclaration) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, Node annotationNode, Annotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handle((CompilationUnitDeclaration) annotationNode.top().get(), annotationNode, annotation); annotationNode.setHandled(); } @Override public void visitAnnotationOnLocal(LocalDeclaration local, Node annotationNode, Annotation annotation) { if ( annotationNode.isHandled() ) return; - handlers.handle((CompilationUnitDeclaration) annotationNode.top().node, annotationNode, annotation); + handlers.handle((CompilationUnitDeclaration) annotationNode.top().get(), annotationNode, annotation); annotationNode.setHandled(); } @Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, Node annotationNode, Annotation annotation) { if ( annotationNode.isHandled() ) return; - handlers.handle((CompilationUnitDeclaration) annotationNode.top().node, annotationNode, annotation); + handlers.handle((CompilationUnitDeclaration) annotationNode.top().get(), annotationNode, annotation); annotationNode.setHandled(); } @Override public void visitAnnotationOnType(TypeDeclaration type, Node annotationNode, Annotation annotation) { if ( annotationNode.isHandled() ) return; - handlers.handle((CompilationUnitDeclaration) annotationNode.top().node, annotationNode, annotation); + handlers.handle((CompilationUnitDeclaration) annotationNode.top().get(), annotationNode, annotation); annotationNode.setHandled(); } } diff --git a/src/lombok/eclipse/handlers/HandleGetter_ecj.java b/src/lombok/eclipse/handlers/HandleGetter_ecj.java index f9bbb884..cde71f83 100644 --- a/src/lombok/eclipse/handlers/HandleGetter_ecj.java +++ b/src/lombok/eclipse/handlers/HandleGetter_ecj.java @@ -4,6 +4,7 @@ import java.lang.reflect.Modifier; import lombok.AccessLevel; import lombok.Getter; +import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseAST.Node; @@ -28,14 +29,14 @@ public class HandleGetter_ecj implements EclipseAnnotationHandler<Getter> { annotationNode.addWarning(String.format("Not generating %s(): A method with that name already exists", methodName)); } - @Override public void handle(Getter annotation, Annotation ast, Node annotationNode) { - if ( !(annotationNode.up().getEclipseNode() instanceof FieldDeclaration) ) return; - FieldDeclaration field = (FieldDeclaration) annotationNode.up().getEclipseNode(); + @Override public void handle(AnnotationValues<Getter> annotation, Annotation ast, Node annotationNode) { + if ( !(annotationNode.up().get() instanceof FieldDeclaration) ) return; + FieldDeclaration field = (FieldDeclaration) annotationNode.up().get(); TypeReference fieldType = field.type; String getterName = TransformationsUtil.toGetterName( new String(field.name), nameEquals(fieldType.getTypeName(), "boolean")); - TypeDeclaration parent = (TypeDeclaration) annotationNode.up().up().getEclipseNode(); + TypeDeclaration parent = (TypeDeclaration) annotationNode.up().up().get(); if ( parent.methods != null ) for ( AbstractMethodDeclaration method : parent.methods ) { if ( method.selector != null && new String(method.selector).equals(getterName) ) { generateDuplicateGetterWarning(annotationNode, getterName); @@ -44,7 +45,7 @@ public class HandleGetter_ecj implements EclipseAnnotationHandler<Getter> { } MethodDeclaration method = new MethodDeclaration(parent.compilationResult); - method.modifiers = toModifier(annotation.value()); + method.modifiers = toModifier(annotation.getInstance().value()); method.returnType = field.type; method.annotations = null; method.arguments = null; diff --git a/src/lombok/javac/HandlerLibrary.java b/src/lombok/javac/HandlerLibrary.java index 6f33003f..f8fd0d75 100644 --- a/src/lombok/javac/HandlerLibrary.java +++ b/src/lombok/javac/HandlerLibrary.java @@ -1,13 +1,9 @@ package lombok.javac; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.Proxy; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -16,19 +12,28 @@ import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; +import lombok.core.AnnotationValues; import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.core.TypeResolver; +import lombok.core.AnnotationValues.AnnotationValue; +import lombok.core.AnnotationValues.AnnotationValueDecodeFail; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCNewArray; public class HandlerLibrary { + private final TypeLibrary typeLibrary = new TypeLibrary(); private final Map<String, AnnotationHandlerContainer<?>> annotationHandlers = new HashMap<String, AnnotationHandlerContainer<?>>(); // private final Collection<JavacASTVisitor> visitorHandlers = new ArrayList<JavacASTVisitor>(); @@ -41,9 +46,56 @@ public class HandlerLibrary { this.annotationClass = annotationClass; } - @SuppressWarnings("unchecked") - public void handle(JavacNode node, Object annInstance) { - handler.handle(node, (T) annInstance); + private Object calculateGuess(JCExpression expr) { + if ( expr instanceof JCLiteral ) { + return ((JCLiteral)expr).value; + } else if ( expr instanceof JCIdent || expr instanceof JCFieldAccess ) { + String x = expr.toString(); + if ( x.endsWith(".class") ) x = x.substring(0, x.length() - 6); + else { + int idx = x.lastIndexOf('.'); + if ( idx > -1 ) x = x.substring(idx + 1); + } + return x; + } else return null; + } + + public void handle(JavacAST.Node node) { + Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); + JCAnnotation anno = (JCAnnotation) node.get(); + List<JCExpression> arguments = anno.getArguments(); + for ( Method m : annotationClass.getDeclaredMethods() ) { + if ( !Modifier.isPublic(m.getModifiers()) ) continue; + String name = m.getName(); + List<String> raws = new ArrayList<String>(); + List<Object> guesses = new ArrayList<Object>(); + + for ( JCExpression arg : arguments ) { + JCAssign assign = (JCAssign) arg; + String mName = assign.lhs.toString(); + if ( !mName.equals(name) ) continue; + JCExpression rhs = assign.rhs; + if ( rhs instanceof JCNewArray ) { + List<JCExpression> elems = ((JCNewArray)rhs).elems; + for ( JCExpression inner : elems ) { + raws.add(inner.toString()); + guesses.add(calculateGuess(inner)); + } + } else { + raws.add(rhs.toString()); + guesses.add(calculateGuess(rhs)); + } + } + + values.put(name, new AnnotationValue(node, raws, guesses) { + @Override public void setError(String message, int valueIdx) { + //TODO + super.setError(message, valueIdx); + } + }); + } + + handler.handle(new AnnotationValues<T>(annotationClass, values, node), (JCAnnotation)node.get(), node); } } @@ -68,6 +120,7 @@ public class HandlerLibrary { messager.printMessage(Diagnostic.Kind.WARNING, "Duplicate handlers for annotation type: " + container.annotationClass.getName()); } + lib.typeLibrary.addType(container.annotationClass.getName()); } catch ( ServiceConfigurationError e ) { messager.printMessage(Diagnostic.Kind.WARNING, "Can't load Lombok annotation handler for javac: " + e); @@ -75,144 +128,26 @@ public class HandlerLibrary { } } - public void handleAnnotation(JavacNode node, TypeElement annotationType) { - AnnotationHandlerContainer<?> container = annotationHandlers.get(annotationType.getQualifiedName().toString()); - if ( container == null ) return; - try { - container.handle(node, createAnnotation(container.annotationClass, annotationType.getQualifiedName(), node)); - } catch ( AnnotationValueDecodeFail e ) { - node.addError(e.getMessage(), e.mirror, e.value); - } - } - - private Object createAnnotation(Class<? extends Annotation> target, Name annotationName, JavacNode node) - throws AnnotationValueDecodeFail { - AnnotationMirror mirror = fetchMirror(annotationName, node); - if ( mirror == null ) throw new AssertionError("This can't be."); - - InvocationHandler invocations = new AnnotationMirrorInvocationHandler(target, mirror); - return Proxy.newProxyInstance(target.getClassLoader(), new Class[] { target }, invocations); - } - - private static class AnnotationValueDecodeFail extends Exception { - private static final long serialVersionUID = 1L; - - AnnotationMirror mirror; - AnnotationValue value; - - AnnotationValueDecodeFail(String msg, AnnotationMirror mirror, AnnotationValue value) { - super(msg); - this.mirror = mirror; - this.value = value; - } - } - - private static class AnnotationMirrorInvocationHandler implements InvocationHandler { - private final AnnotationMirror mirror; - private final Map<String, Object> values = new HashMap<String, Object>(); - - AnnotationMirrorInvocationHandler(Class<?> target, AnnotationMirror mirror) throws AnnotationValueDecodeFail { - this.mirror = mirror; - - for ( Method m : target.getDeclaredMethods() ) { - if ( !Modifier.isPublic(m.getModifiers()) ) continue; - values.put(m.getName(), decode(m)); - } - } - - private Object decode(Method m) throws AnnotationValueDecodeFail { - for ( Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : - mirror.getElementValues().entrySet() ) { - - if ( entry.getKey().getSimpleName().contentEquals(m.getName()) ) { - AnnotationValue value = entry.getValue(); - return convert(m.getReturnType(), mirror, value, value.getValue()); - } - } + public void handleAnnotation(JCCompilationUnit unit, JavacAST.Node node, JCAnnotation annotation) { + TypeResolver resolver = new TypeResolver(typeLibrary, node.getPackageDeclaration(), node.getImportStatements()); + String rawType = annotation.annotationType.toString(); + for ( String fqn : resolver.findTypeMatches(node, rawType) ) { + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if ( container == null ) continue; - return m.getDefaultValue(); - } - - @Override public Object invoke(Object proxy, Method method, Object[] args) { - return values.get(method.getName()); - } - - private Object convert(Class<?> expected, AnnotationMirror mirror, AnnotationValue value, Object v) throws AnnotationValueDecodeFail { - if ( expected == int.class ) { - if ( v instanceof Number ) return ((Number)v).intValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == long.class ) { - if ( v instanceof Number ) return ((Number)v).longValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == short.class ) { - if ( v instanceof Number ) return ((Number)v).shortValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == byte.class ) { - if ( v instanceof Number ) return ((Number)v).byteValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == double.class ) { - if ( v instanceof Number ) return ((Number)v).doubleValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == float.class ) { - if ( v instanceof Number ) return ((Number)v).floatValue(); - else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); - } else if ( expected == char.class ) { - if ( v instanceof Character ) return v; - else throw new AnnotationValueDecodeFail("Expected a character here", mirror, value); - } else if ( expected == boolean.class ) { - if ( v instanceof Boolean ) return v; - else throw new AnnotationValueDecodeFail("Expected a boolean here", mirror, value); - } else if ( expected == String.class ) { - if ( v instanceof String ) return v; - else throw new AnnotationValueDecodeFail("Expected a String here", mirror, value); - } else if ( expected == Class.class ) { - if ( v instanceof TypeMirror ) { - try { - return Class.forName(v.toString()); - } catch ( ClassNotFoundException e ) { - throw new AnnotationValueDecodeFail( - "I can't find this class. Lombok only works well with types in the core java libraries.", - mirror, value); - } - } else throw new AnnotationValueDecodeFail("Expected a class literal here", mirror, value); - } else if ( Enum.class.isAssignableFrom(expected) ) { - if ( v instanceof VariableElement ) { - String n = ((VariableElement)v).getSimpleName().toString(); - @SuppressWarnings("unchecked") - Object enumVal = Enum.valueOf((Class<? extends Enum>)expected, n); - return enumVal; - } else throw new AnnotationValueDecodeFail("Expected an enum value here", mirror, value); - } else if ( expected.isArray() ) { - if ( v instanceof Collection<?> ) { - List<Object> convertedValues = new ArrayList<Object>(); - Class<?> componentType = expected.getComponentType(); - for ( Object innerV : (Collection<?>)v ) { - convertedValues.add(convert(componentType, mirror, value, innerV)); - } - - Object array = Array.newInstance(componentType, convertedValues.size()); - int pos = 0; - for ( Object converted : convertedValues ) Array.set(array, pos++, converted); - return array; - } else throw new AnnotationValueDecodeFail("Expected an array value here", mirror, value); -// Collection<AnnotationValue> result = (Collection<AnnotationValue>)entry.getValue().getValue(); -// return result; - } else { - throw new AssertionError("We didn't know this is even a legal annotation type: " + expected); + try { + container.handle(node); + } catch ( AnnotationValueDecodeFail fail ) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch ( Throwable t ) { + t.printStackTrace(); +// Eclipse.error(String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); + //TODO } } } - private AnnotationMirror fetchMirror(Name lookingFor, JavacNode node) { - for ( AnnotationMirror mirror : node.getJavacAST().getAnnotationMirrors() ) { - if ( !lookingFor.contentEquals( - ((TypeElement)(mirror.getAnnotationType()).asElement()).getQualifiedName()) ) continue; - return mirror; - } - return null; - } - - public void handleType(TypeElement typeElement) { + public void handleAST(JavacAST ast) { //Later! } diff --git a/src/lombok/javac/JavacAST.java b/src/lombok/javac/JavacAST.java new file mode 100644 index 00000000..f56477fb --- /dev/null +++ b/src/lombok/javac/JavacAST.java @@ -0,0 +1,329 @@ +package lombok.javac; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.annotation.processing.Messager; + +import lombok.core.AST; + +import com.sun.source.util.Trees; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.Name; + +public class JavacAST extends AST<JCTree> { + private final Trees trees; + private final JavacProcessingEnvironment env; + private final Messager messager; + private final Name.Table nameTable; + private final TreeMaker treeMaker; + + public JavacAST(Trees trees, JavacProcessingEnvironment env, JCCompilationUnit top) { + super(top.sourcefile == null ? null : top.sourcefile.toString()); + setTop(buildCompilationUnit(top)); + this.trees = trees; + this.env = env; + this.messager = env.getMessager(); + this.nameTable = Name.Table.instance(env.getContext()); + this.treeMaker = TreeMaker.instance(env.getContext()); + } + + @Override public String getPackageDeclaration() { + JCCompilationUnit unit = (JCCompilationUnit)top().get(); + return unit.pid instanceof JCFieldAccess ? unit.pid.toString() : null; + } + + @Override public Collection<String> getImportStatements() { + List<String> imports = new ArrayList<String>(); + JCCompilationUnit unit = (JCCompilationUnit)top().get(); + for ( JCTree def : unit.defs ) { + if ( def instanceof JCImport ) { + imports.add(((JCImport)def).qualid.toString()); + } + } + + return imports; + } + + public void traverse(JavacASTVisitor visitor) { + Node current = top(); + visitor.visitCompilationUnit(current, (JCCompilationUnit)current.get()); + traverseChildren(visitor, current); + visitor.endVisitCompilationUnit(current, (JCCompilationUnit)current.get()); + } + + private void traverseChildren(JavacASTVisitor visitor, Node node) { + for ( Node child : node.down() ) { + JCTree n = child.get(); + + switch ( child.getKind() ) { + case TYPE: + visitor.visitType(child, (JCClassDecl)n); + traverseChildren(visitor, child); + visitor.endVisitType(child, (JCClassDecl)n); + break; + case FIELD: + visitor.visitField(child, (JCVariableDecl)n); + traverseChildren(visitor, child); + visitor.endVisitField(child, (JCVariableDecl)n); + break; + case METHOD: + visitor.visitMethod(child, (JCMethodDecl)n); + traverseChildren(visitor, child); + visitor.endVisitMethod(child, (JCMethodDecl)n); + break; + case INITIALIZER: + visitor.visitInitializer(child, (JCBlock)n); + traverseChildren(visitor, child); + visitor.endVisitInitializer(child, (JCBlock)n); + break; + case ARGUMENT: + JCMethodDecl parent = (JCMethodDecl) child.up().get(); + visitor.visitMethodArgument(child, (JCVariableDecl)n, parent); + traverseChildren(visitor, child); + visitor.endVisitMethodArgument(child, (JCVariableDecl)n, parent); + break; + case LOCAL: + visitor.visitLocal(child, (JCVariableDecl)n); + traverseChildren(visitor, child); + visitor.endVisitLocal(child, (JCVariableDecl)n); + break; + case STATEMENT: + visitor.visitStatement(child, (JCTree)n); + traverseChildren(visitor, child); + visitor.endVisitStatement(node, (JCTree)n); + break; + case ANNOTATION: + switch ( child.up().getKind() ) { + case TYPE: + visitor.visitAnnotationOnType((JCClassDecl)child.up().get(), child, (JCAnnotation)n); + break; + case FIELD: + visitor.visitAnnotationOnField((JCVariableDecl)child.up().get(), child, (JCAnnotation)n); + break; + case METHOD: + visitor.visitAnnotationOnMethod((JCMethodDecl)child.up().get(), child, (JCAnnotation)n); + break; + case ARGUMENT: + JCVariableDecl argument = (JCVariableDecl)child.up().get(); + JCMethodDecl method = (JCMethodDecl)child.up().up().get(); + visitor.visitAnnotationOnMethodArgument(argument, method, child, (JCAnnotation)n); + break; + case LOCAL: + visitor.visitAnnotationOnLocal((JCVariableDecl)child.up().get(), child, (JCAnnotation)n); + break; + default: + throw new AssertionError("Can't be reached"); + } + break; + default: + throw new AssertionError("Can't be reached: " + child.getKind()); + } + } + } + + @Override public Node top() { + return (Node) super.top(); + } + + @Override public Node get(JCTree astNode) { + return (Node) super.get(astNode); + } + + public Name toName(String name) { + return nameTable.fromString(name); + } + + public TreeMaker getTreeMaker() { + return treeMaker; + } + + private Node buildCompilationUnit(JCCompilationUnit top) { + List<Node> childNodes = new ArrayList<Node>(); + for ( JCTree s : top.defs ) { + if ( s instanceof JCClassDecl ) { + addIfNotNull(childNodes, buildType((JCClassDecl)s)); + } // else they are import statements, which we don't care about. Or Skip objects, whatever those are. + } + + return new Node(top, childNodes, Kind.COMPILATION_UNIT); + } + + private Node buildType(JCClassDecl type) { + if ( alreadyHandled(type) ) return null; + List<Node> childNodes = new ArrayList<Node>(); + + for ( JCTree def : type.defs ) { + for ( JCAnnotation annotation : type.mods.annotations ) addIfNotNull(childNodes, buildAnnotation(annotation)); + /* A def can be: + * JCClassDecl for inner types + * JCMethodDecl for constructors and methods + * JCVariableDecl for fields + * JCBlock for (static) initializers + */ + if ( def instanceof JCMethodDecl ) addIfNotNull(childNodes, buildMethod((JCMethodDecl)def)); + else if ( def instanceof JCClassDecl ) addIfNotNull(childNodes, buildType((JCClassDecl)def)); + else if ( def instanceof JCVariableDecl ) addIfNotNull(childNodes, buildField((JCVariableDecl)def)); + else if ( def instanceof JCBlock ) addIfNotNull(childNodes, buildInitializer((JCBlock)def)); + } + + return putInMap(new Node(type, childNodes, Kind.TYPE)); + } + + private Node buildField(JCVariableDecl field) { + if ( alreadyHandled(field) ) return null; + List<Node> childNodes = new ArrayList<Node>(); + for ( JCAnnotation annotation : field.mods.annotations ) addIfNotNull(childNodes, buildAnnotation(annotation)); + addIfNotNull(childNodes, buildExpression(field.init)); + return putInMap(new Node(field, childNodes, Kind.FIELD)); + } + + private Node buildLocalVar(JCVariableDecl local) { + if ( alreadyHandled(local) ) return null; + List<Node> childNodes = new ArrayList<Node>(); + for ( JCAnnotation annotation : local.mods.annotations ) addIfNotNull(childNodes, buildAnnotation(annotation)); + addIfNotNull(childNodes, buildExpression(local.init)); + return putInMap(new Node(local, childNodes, Kind.LOCAL)); + } + + private Node buildInitializer(JCBlock initializer) { + if ( alreadyHandled(initializer) ) return null; + List<Node> childNodes = new ArrayList<Node>(); + for ( JCStatement statement: initializer.stats ) addIfNotNull(childNodes, buildStatement(statement)); + return putInMap(new Node(initializer, childNodes, Kind.INITIALIZER)); + } + + private Node buildMethod(JCMethodDecl method) { + if ( alreadyHandled(method) ) return null; + List<Node> childNodes = new ArrayList<Node>(); + for ( JCAnnotation annotation : method.mods.annotations ) addIfNotNull(childNodes, buildAnnotation(annotation)); + for ( JCVariableDecl param : method.params ) addIfNotNull(childNodes, buildLocalVar(param)); + if ( method.body != null && method.body.stats != null ) + for ( JCStatement statement : method.body.stats ) addIfNotNull(childNodes, buildStatement(statement)); + return putInMap(new Node(method, childNodes, Kind.METHOD)); + } + + private Node buildAnnotation(JCAnnotation annotation) { + if ( alreadyHandled(annotation) ) return null; + return putInMap(new Node(annotation, null, Kind.ANNOTATION)); + } + + private Node buildExpression(JCExpression expression) { + return buildStatementOrExpression(expression); + } + + private Node buildStatement(JCStatement statement) { + return buildStatementOrExpression(statement); + } + + private Node buildStatementOrExpression(JCTree statement) { + if ( statement == null || alreadyHandled(statement) ) return null; + if ( statement instanceof JCAnnotation ) return null; + if ( statement instanceof JCClassDecl ) return buildType((JCClassDecl)statement); + if ( statement instanceof JCVariableDecl ) return buildLocalVar((JCVariableDecl)statement); + + //We drill down because LocalDeclarations and TypeDeclarations can occur anywhere, even in, say, + //an if block, or even the expression on an assert statement! + + setAsHandled(statement); + return drill(statement); + } + + private Node drill(JCTree statement) { + List<Node> childNodes = new ArrayList<Node>(); + for ( FieldAccess fa : fieldsOf(statement.getClass()) ) childNodes.addAll(buildWithField(Node.class, statement, fa)); + return putInMap(new Node(statement, childNodes, Kind.STATEMENT)); + } + + protected Collection<Class<? extends JCTree>> getStatementTypes() { + Collection<Class<? extends JCTree>> collection = new ArrayList<Class<? extends JCTree>>(2); + collection.add(JCStatement.class); + collection.add(JCExpression.class); + return collection; + } + + private static void addIfNotNull(Collection<Node> nodes, Node node) { + if ( node != null ) nodes.add(node); + } + + public class Node extends AST<JCTree>.Node { + public Node(JCTree node, Collection<Node> children, Kind kind) { + super(node, children, kind); + } + + @Override public String getName() { + final Name n; + + if ( node instanceof JCClassDecl ) n = ((JCClassDecl)node).name; + else if ( node instanceof JCMethodDecl ) n = ((JCMethodDecl)node).name; + else if ( node instanceof JCVariableDecl ) n = ((JCVariableDecl)node).name; + else n = null; + + return n == null ? null : n.toString(); + } + + @Override protected boolean calculateIsStructurallySignificant() { + if ( node instanceof JCClassDecl ) return true; + if ( node instanceof JCMethodDecl ) return true; + if ( node instanceof JCVariableDecl ) return true; + if ( node instanceof JCCompilationUnit ) return true; + return false; + } + + public TreeMaker getTreeMaker() { + return treeMaker; + } + + public Name toName(String name) { + return JavacAST.this.toName(name); + } + + /** {@inheritDoc} */ + @Override public Node directUp() { + return (Node) super.directUp(); + } + + /** {@inheritDoc} */ + @Override public Node up() { + return (Node) super.up(); + } + + /** {@inheritDoc} */ + @Override public Node top() { + return (Node) super.top(); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public Collection<Node> down() { + return (Collection<Node>) children; + } + + public void addError(String message) { + System.err.println("ERR: " + message); + //TODO + } + + public void addWarning(String message) { + System.err.println("WARN: " + message); + //TODO + } + } + + @Override protected Node buildStatement(Object statement) { + return buildStatementOrExpression((JCTree) statement); + } +} diff --git a/src/lombok/javac/JavacASTAdapter.java b/src/lombok/javac/JavacASTAdapter.java new file mode 100644 index 00000000..5d678f44 --- /dev/null +++ b/src/lombok/javac/JavacASTAdapter.java @@ -0,0 +1,35 @@ +package lombok.javac; + +import lombok.javac.JavacAST.Node; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; + +public class JavacASTAdapter implements JavacASTVisitor { + @Override public void visitCompilationUnit(Node top, JCCompilationUnit unit) {} + @Override public void endVisitCompilationUnit(Node top, JCCompilationUnit unit) {} + @Override public void visitType(Node typeNode, JCClassDecl type) {} + @Override public void visitAnnotationOnType(JCClassDecl type, Node annotationNode, JCAnnotation annotation) {} + @Override public void endVisitType(Node typeNode, JCClassDecl type) {} + @Override public void visitField(Node fieldNode, JCVariableDecl field) {} + @Override public void visitAnnotationOnField(JCVariableDecl field, Node annotationNode, JCAnnotation annotation) {} + @Override public void endVisitField(Node fieldNode, JCVariableDecl field) {} + @Override public void visitInitializer(Node initializerNode, JCBlock initializer) {} + @Override public void endVisitInitializer(Node initializerNode, JCBlock initializer) {} + @Override public void visitMethod(Node methodNode, JCMethodDecl method) {} + @Override public void visitAnnotationOnMethod(JCMethodDecl method, Node annotationNode, JCAnnotation annotation) {} + @Override public void endVisitMethod(Node methodNode, JCMethodDecl method) {} + @Override public void visitMethodArgument(Node argumentNode, JCVariableDecl argument, JCMethodDecl method) {} + @Override public void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, Node annotationNode, JCAnnotation annotation) {} + @Override public void endVisitMethodArgument(Node argumentNode, JCVariableDecl argument, JCMethodDecl method) {} + @Override public void visitLocal(Node localNode, JCVariableDecl local) {} + @Override public void visitAnnotationOnLocal(JCVariableDecl local, Node annotationNode, JCAnnotation annotation) {} + @Override public void endVisitLocal(Node localNode, JCVariableDecl local) {} + @Override public void visitStatement(Node statementNode, JCTree statement) {} + @Override public void endVisitStatement(Node statementNode, JCTree statement) {} +} diff --git a/src/lombok/javac/JavacASTVisitor.java b/src/lombok/javac/JavacASTVisitor.java new file mode 100644 index 00000000..daeb6b80 --- /dev/null +++ b/src/lombok/javac/JavacASTVisitor.java @@ -0,0 +1,182 @@ +package lombok.javac; + +import lombok.javac.JavacAST.Node; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; + +public interface JavacASTVisitor { + /** + * Called at the very beginning and end. + */ + void visitCompilationUnit(Node top, JCCompilationUnit unit); + void endVisitCompilationUnit(Node top, JCCompilationUnit unit); + + /** + * Called when visiting a type (a class, interface, annotation, enum, etcetera). + */ + void visitType(Node typeNode, JCClassDecl type); + void visitAnnotationOnType(JCClassDecl type, Node annotationNode, JCAnnotation annotation); + void endVisitType(Node typeNode, JCClassDecl type); + + /** + * Called when visiting a field of a class. + */ + void visitField(Node fieldNode, JCVariableDecl field); + void visitAnnotationOnField(JCVariableDecl field, Node annotationNode, JCAnnotation annotation); + void endVisitField(Node fieldNode, JCVariableDecl field); + + /** + * Called for static and instance initializers. You can tell the difference via the isStatic() method. + */ + void visitInitializer(Node initializerNode, JCBlock initializer); + void endVisitInitializer(Node initializerNode, JCBlock initializer); + + /** + * Called for both methods and constructors. + */ + void visitMethod(Node methodNode, JCMethodDecl method); + void visitAnnotationOnMethod(JCMethodDecl method, Node annotationNode, JCAnnotation annotation); + void endVisitMethod(Node methodNode, JCMethodDecl method); + + /** + * Visits a method argument. + */ + void visitMethodArgument(Node argumentNode, JCVariableDecl argument, JCMethodDecl method); + void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, Node annotationNode, JCAnnotation annotation); + void endVisitMethodArgument(Node argumentNode, JCVariableDecl argument, JCMethodDecl method); + + /** + * Visits a local declaration - that is, something like 'int x = 10;' on the method level. Also called + * for method parameters. + */ + void visitLocal(Node localNode, JCVariableDecl local); + void visitAnnotationOnLocal(JCVariableDecl local, Node annotationNode, JCAnnotation annotation); + void endVisitLocal(Node localNode, JCVariableDecl local); + + /** + * Visits a statement that isn't any of the other visit methods (e.g. JCClassDecl). + * The statement object is guaranteed to be either a JCStatement or a JCExpression. + */ + void visitStatement(Node statementNode, JCTree statement); + void endVisitStatement(Node statementNode, JCTree statement); + + public static class JavacASTPrinter implements JavacASTVisitor { + int indent = 0; + private void print(String text, Object... params) { + StringBuilder sb = new StringBuilder(); + for ( int i = 0 ; i < indent ; i++ ) sb.append(" "); + System.out.printf(sb.append(text).append('\n').toString(), params); + } + + @Override public void visitCompilationUnit(Node Node, JCCompilationUnit unit) { + System.out.println("---------------------------------------------------------"); + + print("<CU %s>", Node.getFileName()); + indent++; + } + + @Override public void endVisitCompilationUnit(Node node, JCCompilationUnit unit) { + indent--; + print("</CUD>"); + } + + @Override public void visitType(Node node, JCClassDecl type) { + print("<TYPE %s>", type.name); + indent++; + } + + @Override public void visitAnnotationOnType(JCClassDecl type, Node node, JCAnnotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitType(Node node, JCClassDecl type) { + indent--; + print("</TYPE %s>", type.name); + } + + @Override public void visitInitializer(Node node, JCBlock initializer) { + print("<%s INITIALIZER>", + initializer.isStatic() ? "static" : "instance"); + indent++; + } + + @Override public void endVisitInitializer(Node node, JCBlock initializer) { + indent--; + print("</%s INITIALIZER>", initializer.isStatic() ? "static" : "instance"); + } + + @Override public void visitField(Node node, JCVariableDecl field) { + print("<FIELD %s %s>", field.type, field.name); + indent++; + } + + @Override public void visitAnnotationOnField(JCVariableDecl field, Node node, JCAnnotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitField(Node node, JCVariableDecl field) { + indent--; + print("</FIELD %s %s>", field.type, field.name); + } + + @Override public void visitMethod(Node node, JCMethodDecl method) { + String type = method.name.contentEquals("<init>") ? "CONSTRUCTOR" : "METHOD"; + print("<%s %s>", type, method.name); + indent++; + } + + @Override public void visitAnnotationOnMethod(JCMethodDecl method, Node node, JCAnnotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitMethod(Node node, JCMethodDecl method) { + String type = method.name.contentEquals("<init>") ? "CONSTRUCTOR" : "METHOD"; + indent--; + print("</%s %s>", type, method.name); + } + + @Override public void visitMethodArgument(Node node, JCVariableDecl arg, JCMethodDecl method) { + print("<METHOARG %s %s = %s>", arg.type, arg.name); + indent++; + } + + @Override public void visitAnnotationOnMethodArgument(JCVariableDecl arg, JCMethodDecl method, Node nodeAnnotation, JCAnnotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitMethodArgument(Node node, JCVariableDecl arg, JCMethodDecl method) { + indent--; + print("</METHODARG %s %s>", arg.type, arg.name); + } + + @Override public void visitLocal(Node node, JCVariableDecl local) { + print("<LOCAL %s %s>", local.type, local.name); + indent++; + } + + @Override public void visitAnnotationOnLocal(JCVariableDecl local, Node node, JCAnnotation annotation) { + print("<ANNOTATION: %s />", annotation); + } + + @Override public void endVisitLocal(Node node, JCVariableDecl local) { + indent--; + print("</LOCAL %s %s>", local.type, local.name); + } + + @Override public void visitStatement(Node node, JCTree statement) { + print("<%s>", statement.getClass()); + indent++; + } + + @Override public void endVisitStatement(Node node, JCTree statement) { + indent--; + print("</%s>", statement.getClass()); + } + } +} diff --git a/src/lombok/javac/JavacAnnotationHandler.java b/src/lombok/javac/JavacAnnotationHandler.java index 67542a12..ce3c5fd7 100644 --- a/src/lombok/javac/JavacAnnotationHandler.java +++ b/src/lombok/javac/JavacAnnotationHandler.java @@ -2,6 +2,10 @@ package lombok.javac; import java.lang.annotation.Annotation; +import lombok.core.AnnotationValues; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; + public interface JavacAnnotationHandler<T extends Annotation> { - void handle(JavacNode annotedElement, T annotation); + void handle(AnnotationValues<T> annotation, JCAnnotation ast, JavacAST.Node annotedElement); } diff --git a/src/lombok/javac/JavacNode.java b/src/lombok/javac/JavacNode.java deleted file mode 100644 index 2e65e1d1..00000000 --- a/src/lombok/javac/JavacNode.java +++ /dev/null @@ -1,63 +0,0 @@ -package lombok.javac; - -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; - -import com.sun.source.util.Trees; -import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import com.sun.tools.javac.tree.TreeMaker; -import com.sun.tools.javac.tree.JCTree.JCClassDecl; -import com.sun.tools.javac.util.Name; - -public class JavacNode { - private final Element node; - private final Messager messager; - private final JavacProcessingEnvironment env; - private final Trees trees; - - public JavacNode(Trees trees, JavacProcessingEnvironment env, Element node) { - this.trees = trees; - this.env = env; - this.node = node; - this.messager = env.getMessager(); - } - - public Element getJavacAST() { - return node; - } - - public JCClassDecl getEnclosingType() { - Element parent = node; - while ( !(parent instanceof TypeElement) ) parent = node.getEnclosingElement(); - TypeElement classElement = (TypeElement)parent; - return (JCClassDecl)trees.getTree(classElement); - } - - public Name.Table createNameTable() { - return Name.Table.instance(env.getContext()); - } - - public TreeMaker createTreeMaker() { - return TreeMaker.instance(env.getContext()); - } - - public void addError(String message) { - this.messager.printMessage(Diagnostic.Kind.ERROR, message, node); - } - - public void addError(String message, AnnotationMirror mirror, AnnotationValue value) { - this.messager.printMessage(Diagnostic.Kind.ERROR, message, node, mirror, value); - } - - public void addWarning(String message) { - this.messager.printMessage(Diagnostic.Kind.WARNING, message, node); - } - - public void addWarning(String message, AnnotationMirror mirror, AnnotationValue value) { - this.messager.printMessage(Diagnostic.Kind.WARNING, message, node, mirror, value); - } -} diff --git a/src/lombok/javac/apt/Processor.java b/src/lombok/javac/apt/Processor.java index 864ef52c..2780ce16 100644 --- a/src/lombok/javac/apt/Processor.java +++ b/src/lombok/javac/apt/Processor.java @@ -1,5 +1,8 @@ package lombok.javac.apt; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -12,10 +15,17 @@ import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import lombok.javac.HandlerLibrary; -import lombok.javac.JavacNode; +import lombok.javac.JavacAST; +import lombok.javac.JavacASTAdapter; +import lombok.javac.JavacAST.Node; import com.sun.source.util.Trees; import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; @SupportedAnnotationTypes("*") @@ -39,23 +49,53 @@ public class Processor extends AbstractProcessor { if ( processingEnv == null ) return false; - for ( TypeElement annotationType : annotations ) { - if ( !handlers.hasHandlerFor(annotationType) ) continue; - for ( Element element : roundEnv.getElementsAnnotatedWith(annotationType) ) { - handlers.handleAnnotation(createNode(element), annotationType); - } + IdentityHashMap<JCCompilationUnit, Void> units = new IdentityHashMap<JCCompilationUnit, Void>(); + for ( Element element : roundEnv.getRootElements() ) units.put(toUnit(element), null); + + List<JavacAST> asts = new ArrayList<JavacAST>(); + + for ( JCCompilationUnit unit : units.keySet() ) asts.add(new JavacAST(trees, processingEnv, unit)); + + for ( JavacAST ast : asts ) { + ast.traverse(new AnnotationVisitor()); + handlers.handleAST(ast); + } + return false; + } + + private class AnnotationVisitor extends JavacASTAdapter { + @Override public void visitAnnotationOnType(JCClassDecl type, Node annotationNode, JCAnnotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handleAnnotation((JCCompilationUnit) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); } - for ( Element element : roundEnv.getRootElements() ) { - if ( element instanceof TypeElement ) { - handlers.handleType((TypeElement)element); - } + @Override public void visitAnnotationOnField(JCVariableDecl field, Node annotationNode, JCAnnotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handleAnnotation((JCCompilationUnit) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); } - return false; + @Override public void visitAnnotationOnMethod(JCMethodDecl method, Node annotationNode, JCAnnotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handleAnnotation((JCCompilationUnit) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, Node annotationNode, JCAnnotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handleAnnotation((JCCompilationUnit) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnLocal(JCVariableDecl local, Node annotationNode, JCAnnotation annotation) { + if ( annotationNode.isHandled() ) return; + handlers.handleAnnotation((JCCompilationUnit) annotationNode.top().get(), annotationNode, annotation); + annotationNode.setHandled(); + } } - private JavacNode createNode(Element element) { - return new JavacNode(trees, processingEnv, element); + private JCCompilationUnit toUnit(Element element) { + return (JCCompilationUnit) trees.getPath(element).getCompilationUnit(); } } diff --git a/src/lombok/javac/handlers/HandleGetter_javac.java b/src/lombok/javac/handlers/HandleGetter_javac.java index 7863a097..8501b65f 100644 --- a/src/lombok/javac/handlers/HandleGetter_javac.java +++ b/src/lombok/javac/handlers/HandleGetter_javac.java @@ -2,17 +2,15 @@ package lombok.javac.handlers; import static lombok.javac.handlers.PKG.*; -import javax.lang.model.element.Element; - import lombok.Getter; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; import lombok.javac.JavacAnnotationHandler; -import lombok.javac.JavacNode; +import lombok.javac.JavacAST; import org.mangosdk.spi.ProviderFor; import com.sun.source.tree.MethodTree; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; @@ -27,26 +25,29 @@ import com.sun.tools.javac.util.Name; @ProviderFor(JavacAnnotationHandler.class) public class HandleGetter_javac implements JavacAnnotationHandler<Getter> { - @Override public void handle(JavacNode node, Getter getter) { - if ( !node.getJavacAST().getKind().isField() ) { + @Override public void handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacAST.Node node) { + if ( node.up().getKind() != Kind.FIELD ) { node.addError("@Getter is only supported on a field."); return; } - JCClassDecl javacClassTree = node.getEnclosingType(); + Getter getter = annotation.getInstance(); + + JCClassDecl javacClassTree = (JCClassDecl) node.up().up().get(); int access = toJavacModifier(getter.value()); - MethodTree getterMethod = createGetter(access, node.getJavacAST(), node.createTreeMaker(), node.createNameTable()); + MethodTree getterMethod = createGetter(access, node.up(), node.getTreeMaker()); javacClassTree.defs = javacClassTree.defs.append((JCTree)getterMethod); } - private MethodTree createGetter(int access, Element field, TreeMaker treeMaker, Name.Table nameTable) { - JCStatement returnStatement = treeMaker.Return(treeMaker.Ident((Symbol)field)); + private MethodTree createGetter(int access, JavacAST.Node field, TreeMaker treeMaker) { + JCVariableDecl fieldNode = (JCVariableDecl) field.get(); + JCStatement returnStatement = treeMaker.Return(treeMaker.Ident(fieldNode.getName())); JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement)); - Name methodName = Name.fromString(nameTable, toGetterName(field)); - JCExpression methodType = treeMaker.Type((Type)field.asType()); + Name methodName = field.toName(toGetterName((JCVariableDecl)field.get())); + JCExpression methodType = fieldNode.type != null ? treeMaker.Type(fieldNode.type) : fieldNode.vartype; List<JCTypeParameter> methodGenericParams = List.nil(); List<JCVariableDecl> parameters = List.nil(); diff --git a/src/lombok/javac/handlers/PKG.java b/src/lombok/javac/handlers/PKG.java index 4622c3ee..9ba3765a 100644 --- a/src/lombok/javac/handlers/PKG.java +++ b/src/lombok/javac/handlers/PKG.java @@ -2,17 +2,16 @@ package lombok.javac.handlers; import java.lang.reflect.Modifier; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import lombok.AccessLevel; import lombok.core.TransformationsUtil; class PKG { - static String toGetterName(Element field) { - CharSequence fieldName = field.getSimpleName(); + static String toGetterName(JCVariableDecl field) { + CharSequence fieldName = field.name; - boolean isBoolean = field.asType().getKind() == TypeKind.BOOLEAN; + boolean isBoolean = field.vartype.toString().equals("boolean"); return TransformationsUtil.toGetterName(fieldName, isBoolean); } |