aboutsummaryrefslogtreecommitdiff
path: root/src/lombok/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/lombok/core')
-rw-r--r--src/lombok/core/AST.java268
-rw-r--r--src/lombok/core/AnnotationValues.java281
-rw-r--r--src/lombok/core/TypeResolver.java81
3 files changed, 630 insertions, 0 deletions
diff --git a/src/lombok/core/AST.java b/src/lombok/core/AST.java
new file mode 100644
index 00000000..99902672
--- /dev/null
+++ b/src/lombok/core/AST.java
@@ -0,0 +1,268 @@
+package lombok.core;
+
+import static lombok.Lombok.sneakyThrow;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class AST<N> {
+ public enum Kind {
+ COMPILATION_UNIT, TYPE, FIELD, INITIALIZER, METHOD, ANNOTATION, ARGUMENT, LOCAL, STATEMENT;
+ }
+
+ private Node top;
+ private final String fileName;
+ private Map<N, Void> identityDetector = new IdentityHashMap<N, Void>();
+ private Map<N, Node> nodeMap = new IdentityHashMap<N, Node>();
+
+ protected AST(String fileName) {
+ this.fileName = fileName == null ? "(unknown).java" : fileName;
+ }
+
+ protected void setTop(Node top) {
+ this.top = top;
+ }
+
+ public abstract String getPackageDeclaration();
+
+ public abstract Collection<String> getImportStatements();
+
+ protected <T extends Node> T putInMap(T parent) {
+ nodeMap.put(parent.get(), parent);
+ identityDetector.put(parent.get(), null);
+ return parent;
+ }
+
+ protected Map<N, Node> getNodeMap() {
+ return nodeMap;
+ }
+
+ protected void clearState() {
+ identityDetector = new IdentityHashMap<N, Void>();
+ nodeMap = new IdentityHashMap<N, Node>();
+ }
+
+ protected boolean alreadyHandled(N node) {
+ return identityDetector.containsKey(node);
+ }
+
+ protected void setAsHandled(N node) {
+ identityDetector.put(node, null);
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public Node top() {
+ return top;
+ }
+
+ public Node get(N node) {
+ return nodeMap.get(node);
+ }
+
+ public abstract class Node {
+ protected final Kind kind;
+ protected final N node;
+ protected final Collection<? extends Node> children;
+ protected Node parent;
+ protected boolean handled;
+ protected boolean isStructurallySignificant;
+
+ protected Node(N node, Collection<? extends Node> children, Kind kind) {
+ this.kind = kind;
+ this.node = node;
+ this.children = children == null ? Collections.<Node>emptyList() : children;
+ for ( Node child : this.children ) child.parent = this;
+ this.isStructurallySignificant = calculateIsStructurallySignificant();
+ }
+
+ public String getPackageDeclaration() {
+ return AST.this.getPackageDeclaration();
+ }
+
+ public Collection<String> getImportStatements() {
+ return AST.this.getImportStatements();
+ }
+
+ protected abstract boolean calculateIsStructurallySignificant();
+
+ public N get() {
+ return node;
+ }
+
+ public Kind getKind() {
+ return kind;
+ }
+
+ /**
+ * Return the name of your type (simple name), method, field, or local variable. Return null if this
+ * node doesn't really have a name, such as initializers, while statements, etc.
+ */
+ public abstract String getName();
+
+ /** Returns the structurally significant node that encloses this one.
+ *
+ * @see #isStructurallySignificant()
+ */
+ public Node up() {
+ Node result = (Node)parent;
+ while ( result != null && !result.isStructurallySignificant ) result = (Node)result.parent;
+ return result;
+ }
+
+ /**
+ * Returns the direct parent node in the AST tree of this node. For example, a local variable declaration's
+ * direct parent can be e.g. an If block, but its up() Node is the Method that contains it.
+ */
+ public Node directUp() {
+ return parent;
+ }
+
+ public Collection<? extends Node> down() {
+ return children;
+ }
+
+ public boolean isHandled() {
+ return handled;
+ }
+
+ public Node setHandled() {
+ this.handled = true;
+ return this;
+ }
+
+ public Node top() {
+ return top;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public abstract void addError(String message);
+
+ public abstract void addWarning(String message);
+
+ /**
+ * Structurally significant means: LocalDeclaration, TypeDeclaration, MethodDeclaration, ConstructorDeclaration,
+ * FieldDeclaration, Initializer, and CompilationUnitDeclaration.
+ * The rest is e.g. if statements, while loops, etc.
+ */
+ public boolean isStructurallySignificant() {
+ return isStructurallySignificant;
+ }
+ }
+
+ protected static class FieldAccess {
+ public final Field field;
+ public final int dim;
+
+ FieldAccess(Field field, int dim) {
+ this.field = field;
+ this.dim = dim;
+ }
+ }
+
+ private static Map<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new HashMap<Class<?>, Collection<FieldAccess>>();
+ protected Collection<FieldAccess> fieldsOf(Class<?> c) {
+ Collection<FieldAccess> fields = fieldsOfASTClasses.get(c);
+ if ( fields != null ) return fields;
+
+ fields = new ArrayList<FieldAccess>();
+ getFields(c, fields);
+ fieldsOfASTClasses.put(c, fields);
+ return fields;
+ }
+
+ private void getFields(Class<?> c, Collection<FieldAccess> fields) {
+ if ( c == Object.class || c == null ) return;
+ for ( Field f : c.getDeclaredFields() ) {
+ if ( Modifier.isStatic(f.getModifiers()) ) continue;
+ Class<?> t = f.getType();
+ int dim = 0;
+
+ if ( t.isArray() ) {
+ while ( t.isArray() ) {
+ dim++;
+ t = t.getComponentType();
+ }
+ } else if ( Collection.class.isAssignableFrom(t) ) {
+ while ( Collection.class.isAssignableFrom(t) ) {
+ dim++;
+ t = getComponentType(f.getGenericType());
+ }
+ }
+
+ for ( Class<?> statementType : getStatementTypes() ) {
+ if ( statementType.isAssignableFrom(t) ) {
+ f.setAccessible(true);
+ fields.add(new FieldAccess(f, dim));
+ break;
+ }
+ }
+ }
+ getFields(c.getSuperclass(), fields);
+ }
+
+ private Class<?> getComponentType(Type type) {
+ if ( type instanceof ParameterizedType ) {
+ Type component = ((ParameterizedType)type).getActualTypeArguments()[0];
+ return component instanceof Class<?> ? (Class<?>)component : Object.class;
+ } else return Object.class;
+ }
+
+ protected abstract Collection<Class<? extends N>> getStatementTypes();
+
+ protected <T extends Node> Collection<T> buildWithField(Class<T> nodeType, N statement, FieldAccess fa) {
+ List<T> list = new ArrayList<T>();
+ buildWithField0(nodeType, statement, fa, list);
+ return list;
+ }
+
+ private <T extends Node> void buildWithField0(Class<T> nodeType, N child, FieldAccess fa, Collection<T> list) {
+ try {
+ Object o = fa.field.get(child);
+ if ( o == null ) return;
+ if ( fa.dim == 0 ) {
+ Node node = buildStatement(o);
+ if ( node != null ) list.add(nodeType.cast(node));
+ } else if ( o.getClass().isArray() ) buildWithArray(nodeType, o, list, fa.dim);
+ else if ( Collection.class.isInstance(o) ) buildWithCollection(nodeType, o, list, fa.dim);
+ } catch ( IllegalAccessException e ) {
+ sneakyThrow(e);
+ }
+ }
+
+ private <T extends Node> void buildWithArray(Class<T> nodeType, Object array, Collection<T> list, int dim) {
+ if ( dim == 1 ) for ( Object v : (Object[])array ) {
+ if ( v == null ) continue;
+ Node node = buildStatement(v);
+ if ( node != null ) list.add(nodeType.cast(node));
+ } else for ( Object v : (Object[])array ) {
+ buildWithArray(nodeType, v, list, dim-1);
+ }
+ }
+
+ private <T extends Node> void buildWithCollection(Class<T> nodeType, Object collection, Collection<T> list, int dim) {
+ if ( dim == 1 ) for ( Object v : (Collection<?>)collection ) {
+ if ( v == null ) continue;
+ Node node = buildStatement(v);
+ if ( node != null ) list.add(nodeType.cast(node));
+ } else for ( Object v : (Collection<?>)collection ) {
+ buildWithCollection(nodeType, v, list, dim-1);
+ }
+ }
+
+ protected abstract Node buildStatement(Object statement);
+}
diff --git a/src/lombok/core/AnnotationValues.java b/src/lombok/core/AnnotationValues.java
new file mode 100644
index 00000000..ec17e34f
--- /dev/null
+++ b/src/lombok/core/AnnotationValues.java
@@ -0,0 +1,281 @@
+package lombok.core;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class AnnotationValues<A extends Annotation> {
+ private final Class<A> type;
+ private final Map<String, AnnotationValue> values;
+ private final AST<?>.Node ast;
+
+ public static class AnnotationValue {
+ public final List<String> raws;
+ public final List<Object> valueGuesses;
+ private final AST<?>.Node node;
+
+ /**
+ * 'raw' should be the exact expression, for example '5+7', 'AccessLevel.PUBLIC', or 'int.class'.
+ * 'valueGuess' should be a likely guess at the real value intended.
+ *
+ * For classes, supply the class name (qualified or not) as a string.<br />
+ * For enums, supply the simple name part (everything after the last dot) as a string.<br />
+ */
+ public AnnotationValue(AST<?>.Node node, String raw, Object valueGuess) {
+ this.node = node;
+ this.raws = Collections.singletonList(raw);
+ this.valueGuesses = Collections.singletonList(valueGuess);
+ }
+
+ /** When the value is an array type. */
+ public AnnotationValue(AST<?>.Node node, List<String> raws, List<Object> valueGuesses) {
+ this.node = node;
+ this.raws = raws;
+ this.valueGuesses = valueGuesses;
+ }
+
+ /**
+ * Override this if you want more specific behaviour (e.g. get the source position just right).
+ *
+ * @param message English message with the problem.
+ * @param valueIdx The index into the values for this annotation key that caused the problem.
+ * -1 for a problem that applies to all values, otherwise the 0-based index into an array of values.
+ * If there is no array for this value (e.g. value=1 instead of value={1,2}), then always -1 or 0.
+ */
+ public void setError(String message, int valueIdx) {
+ node.addError(message);
+ }
+ }
+
+ public AnnotationValues(Class<A> type, Map<String, AnnotationValue> values, AST<?>.Node ast) {
+ this.type = type;
+ this.values = values;
+ this.ast = ast;
+ }
+
+ public static class AnnotationValueDecodeFail extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public final int idx;
+ public final AnnotationValue owner;
+
+ public AnnotationValueDecodeFail(AnnotationValue owner, String msg, int idx) {
+ super(msg);
+ this.idx = idx;
+ this.owner = owner;
+ }
+ }
+
+ private static AnnotationValueDecodeFail makeNoDefaultFail(AnnotationValue owner, Method method) {
+ return new AnnotationValueDecodeFail(owner,
+ "No value supplied but " + method.getName() + " has no default either.", -1);
+ }
+
+ @SuppressWarnings("unchecked")
+ public A getInstance() throws AnnotationValueDecodeFail {
+ InvocationHandler invocations = new InvocationHandler() {
+ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ AnnotationValue v = values.get(method.getName());
+ if ( v == null ) {
+ Object defaultValue = method.getDefaultValue();
+ if ( defaultValue != null ) return defaultValue;
+ throw makeNoDefaultFail(v, method);
+ }
+
+ boolean isArray = false;
+ Class<?> expected = method.getReturnType();
+ Object array = null;
+ if ( expected.isArray() ) {
+ isArray = true;
+ expected = expected.getComponentType();
+ array = Array.newInstance(expected, 1);
+ }
+
+ if ( !isArray && v.valueGuesses.size() > 1 ) {
+ System.out.println(v.valueGuesses.size() + ": " + v.valueGuesses);
+ throw new AnnotationValueDecodeFail(v,
+ "Expected a single value, but " + method.getName() + " has an array of values", -1);
+ }
+
+ if ( v.valueGuesses.size() == 0 && !isArray ) {
+ Object defaultValue = method.getDefaultValue();
+ if ( defaultValue == null ) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ }
+
+ int idx = 0;
+ for ( Object guess : v.valueGuesses ) {
+ Object result = guess == null ? null : guessToType(guess, expected, v, idx);
+ if ( !isArray ) {
+ if ( result == null ) {
+ Object defaultValue = method.getDefaultValue();
+ if ( defaultValue == null ) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ } else return result;
+ } else {
+ if ( result == null ) {
+ if ( v.valueGuesses.size() == 1 ) {
+ Object defaultValue = method.getDefaultValue();
+ if ( defaultValue == null ) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ } else throw new AnnotationValueDecodeFail(v,
+ "I can't make sense of this annotation value. Try using a fully qualified literal.", idx);
+ }
+ Array.set(array, idx++, result);
+ }
+ }
+
+ return array;
+ }
+ };
+
+ return (A) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, invocations);
+ }
+
+ private Object guessToType(Object guess, Class<?> expected, AnnotationValue v, int pos) {
+ if ( expected == int.class ) {
+ if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) {
+ return ((Number)guess).intValue();
+ }
+ }
+
+ if ( expected == long.class ) {
+ if ( guess instanceof Long || guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) {
+ return ((Number)guess).longValue();
+ }
+ }
+
+ if ( expected == short.class ) {
+ if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) {
+ int intVal = ((Number)guess).intValue();
+ int shortVal = ((Number)guess).shortValue();
+ if ( shortVal == intVal ) return shortVal;
+ }
+ }
+
+ if ( expected == byte.class ) {
+ if ( guess instanceof Integer || guess instanceof Short || guess instanceof Byte ) {
+ int intVal = ((Number)guess).intValue();
+ int byteVal = ((Number)guess).byteValue();
+ if ( byteVal == intVal ) return byteVal;
+ }
+ }
+
+ if ( expected == double.class ) {
+ if ( guess instanceof Number ) return ((Number)guess).doubleValue();
+ }
+
+ if ( expected == float.class ) {
+ if ( guess instanceof Number ) return ((Number)guess).floatValue();
+ }
+
+ if ( expected == boolean.class ) {
+ if ( guess instanceof Boolean ) return ((Boolean)guess).booleanValue();
+ }
+
+ if ( expected == char.class ) {
+ if ( guess instanceof Character ) return ((Character)guess).charValue();
+ }
+
+ if ( expected == String.class ) {
+ if ( guess instanceof String ) return expected;
+ }
+
+ if ( Enum.class.isAssignableFrom(expected) ) {
+ if ( guess instanceof String ) {
+ for ( Object enumConstant : expected.getEnumConstants() ) {
+ String target = ((Enum<?>)enumConstant).name();
+ if ( target.equals(guess) ) return enumConstant;
+ }
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate " + guess + " to an enum of type " + expected, pos);
+ }
+ }
+
+ if ( Class.class == expected ) {
+ if ( guess instanceof String ) try {
+ return Class.forName(toFQ((String)guess));
+ } catch ( ClassNotFoundException e ) {
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate " + guess + " to a class object.", pos);
+ }
+ }
+
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate a " + guess.getClass() + " to the expected " + expected, pos);
+ }
+
+ public List<String> getRawExpressions(String annotationMethodName) {
+ AnnotationValue v = values.get(annotationMethodName);
+ return v == null ? Collections.<String>emptyList() : v.raws;
+ }
+
+ public String getRawExpression(String annotationMethodName) {
+ List<String> l = getRawExpressions(annotationMethodName);
+ return l.isEmpty() ? null : l.get(0);
+ }
+
+ public List<String> getProbableFQTypes(String annotationMethodName) {
+ List<String> result = new ArrayList<String>();
+ AnnotationValue v = values.get(annotationMethodName);
+ if ( v == null ) return Collections.emptyList();
+
+ for ( Object o : v.valueGuesses ) result.add(o == null ? null : toFQ(o.toString()));
+ return result;
+ }
+
+ private String toFQ(String typeName) {
+ Class<?> c;
+ boolean fqn = typeName.indexOf('.') > -1;
+ String prefix = fqn ? typeName.substring(0, typeName.indexOf('.')) : typeName;
+
+ for ( String im : ast.getImportStatements() ) {
+ int idx = im.lastIndexOf('.');
+ String simple = im;
+ if ( idx > -1 ) simple = im.substring(idx+1);
+ if ( simple.equals(prefix) ) {
+ return im + typeName.substring(prefix.length());
+ }
+ }
+
+ c = tryClass(typeName);
+ if ( c != null ) return c.getName();
+
+ c = tryClass("java.lang." + typeName);
+ if ( c != null ) return c.getName();
+
+ //Try star imports
+ for ( String im : ast.getImportStatements() ) {
+ if ( im.endsWith(".*") ) {
+ c = tryClass(im.substring(0, im.length() -1) + typeName);
+ if ( c != null ) return c.getName();
+ }
+ }
+
+ if ( !fqn ) {
+ String pkg = ast.getPackageDeclaration();
+ if ( pkg != null ) return pkg + "." + typeName;
+ }
+
+ return null;
+ }
+
+ private Class<?> tryClass(String name) {
+ try {
+ return Class.forName(name);
+ } catch ( ClassNotFoundException e ) {
+ return null;
+ }
+ }
+
+ public String getProbableFQType(String annotationMethodName) {
+ List<String> l = getProbableFQTypes(annotationMethodName);
+ return l.isEmpty() ? null : l.get(0);
+ }
+}
diff --git a/src/lombok/core/TypeResolver.java b/src/lombok/core/TypeResolver.java
new file mode 100644
index 00000000..1e356f89
--- /dev/null
+++ b/src/lombok/core/TypeResolver.java
@@ -0,0 +1,81 @@
+package lombok.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import lombok.core.AST.Kind;
+
+public class TypeResolver {
+ private final TypeLibrary library;
+ private Collection<String> imports;
+
+ public TypeResolver(TypeLibrary library, String packageString, Collection<String> importStrings) {
+ this.library = library;
+ this.imports = makeImportList(packageString, importStrings);
+ }
+
+ private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) {
+ Set<String> imports = new HashSet<String>();
+ if ( packageString != null ) imports.add(packageString + ".*");
+ imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings);
+ return imports;
+ }
+
+ public Collection<String> findTypeMatches(AST<?>.Node context, String typeRef) {
+ Collection<String> potentialMatches = library.findCompatible(typeRef);
+ if ( potentialMatches.isEmpty() ) return Collections.emptyList();
+
+ 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();
+
+ //Check if any of our potentials is even imported in the first place. If not: no matches.
+ potentialMatches = eliminateImpossibleMatches(potentialMatches);
+ 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.
+ AST<?>.Node n = context;
+ while ( n != null ) {
+ if ( n.getKind() == Kind.TYPE ) {
+ String name = n.getName();
+ if ( name != null && name.equals(simpleName) ) return Collections.emptyList();
+ }
+ n = n.up();
+ }
+
+ // The potential matches we found by comparing the import statements is our matching set. Return it.
+ return potentialMatches;
+ }
+
+ private Collection<String> eliminateImpossibleMatches(Collection<String> potentialMatches) {
+ Set<String> results = new HashSet<String>();
+
+ for ( String importedType : imports ) {
+ Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType));
+ reduced.retainAll(potentialMatches);
+ results.addAll(reduced);
+ }
+
+ return results;
+ }
+
+ private boolean nameConflictInImportList(String simpleName, Collection<String> potentialMatches) {
+ for ( String importedType : imports ) {
+ if ( !toSimpleName(importedType).equals(simpleName) ) continue;
+ if ( potentialMatches.contains(importedType) ) continue;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static String toSimpleName(String typeName) {
+ int idx = typeName.lastIndexOf('.');
+ return idx == -1 ? typeName : typeName.substring(idx+1);
+ }
+}