diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-17 10:43:39 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-17 10:43:39 +0200 |
commit | 024d8ffa9801f463fecadd16f42d51bbed46dea7 (patch) | |
tree | acb0b85f79eafb517e3472bd3d906235d1541ade | |
parent | aa6d2e262f3d6c43f6d89220cdc10c6954bb2bdd (diff) | |
download | lombok-024d8ffa9801f463fecadd16f42d51bbed46dea7.tar.gz lombok-024d8ffa9801f463fecadd16f42d51bbed46dea7.tar.bz2 lombok-024d8ffa9801f463fecadd16f42d51bbed46dea7.zip |
Massive refactors. This list isn't complete, but should give you an idea:
A) many things in lombok.eclipse moved to lombok.core to enable reuse with lombok.javac.
B) lombok.javac works now similarly to eclipse's model: We first make big ASTs that are bidirectionally traversable, then we walk through that for annotations.
C) Instead of getting an annotation instance, you now get an object that is more flexible and can e.g. give you class values in an enum as a string instead of a Class object, which may fail if that class isn't on the classpath of lombok.
D) sources to the internal sun classes for javac added to /contrib.
21 files changed, 1565 insertions, 714 deletions
diff --git a/contrib/javac-source.zip b/contrib/javac-source.zip Binary files differnew file mode 100644 index 00000000..7d989ac6 --- /dev/null +++ b/contrib/javac-source.zip 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); } |