aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/lombok/Delegate.java14
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java275
2 files changed, 224 insertions, 65 deletions
diff --git a/src/core/lombok/Delegate.java b/src/core/lombok/Delegate.java
index 4308f6f9..c7a44c39 100644
--- a/src/core/lombok/Delegate.java
+++ b/src/core/lombok/Delegate.java
@@ -37,7 +37,8 @@ import java.lang.annotation.Target;
* will generate for example an {@code boolean add(String)} method, which contains: {@code return foo.add(arg);}, as well as all other methods in {@code List}.
*
* All public instance methods of the field's type, as well as all public instance methods of all the field's type's superfields are delegated, except for all methods
- * that exist in {@link java.lang.Object}, and except for methods that are already present in the class.
+ * that exist in {@link Object}, the {@code canEqual(Object)} method, and any methods that appear in types
+ * that are listed in the {@code excludes} property.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@@ -47,6 +48,15 @@ public @interface Delegate {
* type arguments can only be done as a field type. A solution for this is to create a private inner interface/class with the appropriate types extended, and possibly
* with all methods you'd like to delegate listed, and then supply that class here. The field does not actually have to implement the type you're delegating; the
* type listed here is used only to determine which delegate methods to generate.
+ *
+ * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated.
*/
- Class<?>[] value() default {};
+ Class<?>[] types() default {};
+
+ /**
+ * Each method in any of the types listed here (include supertypes) will <em>not</em> be delegated.
+ *
+ * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated.
+ */
+ Class<?>[] excludes() default {};
}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
index 846232f6..ea54be48 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans.
+ * Copyright © 2010-2011 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -95,12 +94,87 @@ public class PatchDelegate {
.build());
}
- public static boolean handleDelegateForType(ClassScope scope) {
+ private static class ClassScopeEntry {
+ ClassScopeEntry(ClassScope scope) {
+ this.scope = scope;
+ }
+
+ final ClassScope scope;
+ String corruptedPath;
+ }
+
+ private static ThreadLocal<List<ClassScopeEntry>> visited = new ThreadLocal<List<ClassScopeEntry>>() {
+ protected List<ClassScopeEntry> initialValue() {
+ return new ArrayList<ClassScopeEntry>();
+ }
+ };
+
+ private static String nameOfScope(ClassScope scope) {
TypeDeclaration decl = scope.referenceContext;
- if (decl == null) return false;
+ if (decl == null) return "(unknown)";
+ if (decl.name == null || decl.name.length == 0) return "(unknown)";
+ return new String(decl.name);
+ }
+
+ public static boolean handleDelegateForType(ClassScope scope) {
+ List<ClassScopeEntry> stack = visited.get();
+ StringBuilder corrupted = null;
+ for (ClassScopeEntry entry : stack) {
+ if (corrupted != null) {
+ corrupted.append(" -> ").append(nameOfScope(entry.scope));
+ } else if (entry.scope == scope) {
+ corrupted = new StringBuilder().append(nameOfScope(scope));
+ }
+ }
+
+ if (corrupted != null) {
+ boolean found = false;
+ String path = corrupted.toString();
+ for (ClassScopeEntry entry : stack) {
+ if (!found && entry.scope == scope) found = true;
+ if (found) entry.corruptedPath = path;
+ }
+ } else {
+ ClassScopeEntry entry = new ClassScopeEntry(scope);
+ stack.add(entry);
+
+ try {
+ List<BindingTuple> methodsToDelegate = new ArrayList<BindingTuple>();
+ TypeDeclaration decl = scope.referenceContext;
+ if (decl != null) {
+ CompilationUnitDeclaration cud = scope.compilationUnitScope().referenceContext;
+ EclipseAST eclipseAst = TransformEclipseAST.getAST(cud, true);
+ fillMethodBindings(cud, scope, methodsToDelegate);
+ if (entry.corruptedPath != null) {
+ eclipseAst.get(scope.referenceContext).addError("No @Delegate methods created because there's a loop: " + entry.corruptedPath);
+ } else {
+ generateDelegateMethods(eclipseAst.get(decl), methodsToDelegate);
+ }
+ }
+ } finally {
+ stack.remove(stack.size() - 1);
+ }
+ }
- CompilationUnitDeclaration cud = null;
- EclipseAST eclipseAst = null;
+ return false;
+ }
+
+ /**
+ * Returns a string containing the signature of a method that appears (erased) at least twice in the list.
+ * If no duplicates are present, {@code null} is returned.
+ */
+ private static String containsDuplicates(List<BindingTuple> tuples) {
+ Set<String> sigs = new HashSet<String>();
+ for (BindingTuple tuple : tuples) {
+ if (!sigs.add(printSig(tuple.parameterized))) return printSig(tuple.parameterized);
+ }
+
+ return null;
+ }
+
+ private static void fillMethodBindings(CompilationUnitDeclaration cud, ClassScope scope, List<BindingTuple> methodsToDelegate) {
+ TypeDeclaration decl = scope.referenceContext;
+ if (decl == null) return;
if (decl.fields != null) for (FieldDeclaration field : decl.fields) {
if (field.annotations == null) continue;
@@ -110,14 +184,10 @@ public class PatchDelegate {
if (!charArrayEquals("lombok", tb.qualifiedPackageName())) continue;
if (!charArrayEquals("Delegate", tb.qualifiedSourceName())) continue;
- if (cud == null) {
- cud = scope.compilationUnitScope().referenceContext;
- eclipseAst = TransformEclipseAST.getAST(cud, true);
- }
-
List<ClassLiteralAccess> rawTypes = new ArrayList<ClassLiteralAccess>();
+ List<ClassLiteralAccess> excludedRawTypes = new ArrayList<ClassLiteralAccess>();
for (MemberValuePair pair : ann.memberValuePairs()) {
- if (pair.name == null || charArrayEquals("value", pair.name)) {
+ if (charArrayEquals("types", pair.name)) {
if (pair.value instanceof ArrayInitializer) {
for (Expression expr : ((ArrayInitializer)pair.value).expressions) {
if (expr instanceof ClassLiteralAccess) rawTypes.add((ClassLiteralAccess) expr);
@@ -127,56 +197,125 @@ public class PatchDelegate {
rawTypes.add((ClassLiteralAccess) pair.value);
}
}
+ if (charArrayEquals("excludes", pair.name)) {
+ if (pair.value instanceof ArrayInitializer) {
+ for (Expression expr : ((ArrayInitializer)pair.value).expressions) {
+ if (expr instanceof ClassLiteralAccess) excludedRawTypes.add((ClassLiteralAccess) expr);
+ }
+ }
+ if (pair.value instanceof ClassLiteralAccess) {
+ excludedRawTypes.add((ClassLiteralAccess) pair.value);
+ }
+ }
}
- List<BindingPair> methodsToDelegate = new ArrayList<BindingPair>();
+ List<BindingTuple> methodsToExclude = new ArrayList<BindingTuple>();
+ for (ClassLiteralAccess cla : excludedRawTypes) {
+ addAllMethodBindings(methodsToExclude, cla.type.resolveType(decl.initializerScope), new HashSet<String>(), field.name, ann);
+ }
+
+ Set<String> banList = new HashSet<String>();
+ for (BindingTuple excluded : methodsToExclude) banList.add(printSig(excluded.parameterized));
+
+ List<BindingTuple> methodsToDelegateForThisAnn = new ArrayList<BindingTuple>();
if (rawTypes.isEmpty()) {
- addAllMethodBindings(methodsToDelegate, field.type.resolveType(decl.initializerScope));
+ addAllMethodBindings(methodsToDelegateForThisAnn, field.type.resolveType(decl.initializerScope), banList, field.name, ann);
} else {
for (ClassLiteralAccess cla : rawTypes) {
- addAllMethodBindings(methodsToDelegate, cla.type.resolveType(decl.initializerScope));
+ addAllMethodBindings(methodsToDelegateForThisAnn, cla.type.resolveType(decl.initializerScope), banList, field.name, ann);
}
}
- removeExistingMethods(methodsToDelegate, decl, scope);
+ // Not doing this right now because of problems - see commented-out-method for info.
+ // removeExistingMethods(methodsToDelegate, decl, scope);
- generateDelegateMethods(eclipseAst.get(decl), methodsToDelegate, field.name, eclipseAst.get(ann));
- }
- }
-
- return false;
- }
-
- private static void removeExistingMethods(List<BindingPair> list, TypeDeclaration decl, ClassScope scope) {
- for (AbstractMethodDeclaration methodDecl : decl.methods) {
- if (!(methodDecl instanceof MethodDeclaration)) continue;
- MethodDeclaration md = (MethodDeclaration) methodDecl;
- char[] name = md.selector;
- TypeBinding[] args = md.arguments == null ? new TypeBinding[0] : new TypeBinding[md.arguments.length];
- for (int i = 0; i < args.length; i++) {
- TypeReference clone = Eclipse.copyType(md.arguments[i].type, md.arguments[i]);
- args[i] = clone.resolveType(scope).erasure();
- }
- Iterator<BindingPair> it = list.iterator();
- methods:
- while (it.hasNext()) {
- MethodBinding mb = it.next().parameterized;
- if (!Arrays.equals(mb.selector, name)) continue;
- int paramLen = mb.parameters == null ? 0 : mb.parameters.length;
- if (paramLen != args.length) continue;
- for (int i = 0; i < paramLen; i++) {
- if (!mb.parameters[i].erasure().isEquivalentTo(args[i])) continue methods;
+ String dupe = containsDuplicates(methodsToDelegateForThisAnn);
+ if (dupe != null) {
+ EclipseAST eclipseAst = TransformEclipseAST.getAST(cud, true);
+ eclipseAst.get(ann).addError("The method '" + dupe + "' is being delegated by more than one specified type.");
+ } else {
+ methodsToDelegate.addAll(methodsToDelegateForThisAnn);
}
- it.remove(); // Method already exists in this class - don't create a delegating implementation.
}
}
}
- private static void generateDelegateMethods(EclipseNode typeNode, List<BindingPair> methods, char[] delegate, EclipseNode annNode) {
+ /*
+ * We may someday finish this method. Steps to be completed:
+ *
+ * (A) Turn any Parameterized anythings into non-parameterized versions. Resolving parameterized stuff will definitely not work safely.
+ * (B) scope.problemReporter() will need to return a noop reporter as various errors are marked off.
+ * (C) Find a way to do _something_ for references to typevars (i.e. 'T') which are declared on the method itself.
+ * (D) getTypeBinding isn't public, so call it via reflection.
+ */
+// private static TypeBinding safeResolveAndErase(TypeReference ref, Scope scope) {
+// if (ref.resolvedType != null) {
+// return ref.resolvedType.isValidBinding() ? ref.resolvedType : null;
+// }
+//
+// try {
+// TypeBinding bind = ref.getTypeBinding(scope);
+// if (!bind.isValidBinding()) return null;
+// } catch (AbortCompilation e) {
+// return null;
+// }
+// return bind.erasure();
+// }
+
+ /*
+ * Not using this because calling clone.resolveType() taints a bunch of caches and reports erroneous errors.
+ */
+// private static void removeExistingMethods(List<BindingTuple> list, TypeDeclaration decl, ClassScope scope) {
+// for (AbstractMethodDeclaration methodDecl : decl.methods) {
+// if (!(methodDecl instanceof MethodDeclaration)) continue;
+// MethodDeclaration md = (MethodDeclaration) methodDecl;
+// char[] name = md.selector;
+// TypeBinding[] args = md.arguments == null ? new TypeBinding[0] : new TypeBinding[md.arguments.length];
+// for (int i = 0; i < args.length; i++) {
+// TypeReference clone = Eclipse.copyType(md.arguments[i].type, md.arguments[i]);
+// args[i] = clone.resolveType(scope).erasure(); // This is the problematic line
+// }
+// Iterator<BindingTuple> it = list.iterator();
+// methods:
+// while (it.hasNext()) {
+// MethodBinding mb = it.next().parameterized;
+// if (!Arrays.equals(mb.selector, name)) continue;
+// int paramLen = mb.parameters == null ? 0 : mb.parameters.length;
+// if (paramLen != args.length) continue;
+// if (md.typeParameters == null || md.typeParameters.length == 0) {
+// for (int i = 0; i < paramLen; i++) {
+// if (!mb.parameters[i].erasure().isEquivalentTo(args[i])) continue methods;
+// }
+// } else {
+// for (int i = 0; i < paramLen; i++) {
+// if (!mb.parameters[i].erasure().isEquivalentTo(args[i])) ;
+// }
+// //BUG #???: We erase the method's parameter types using the class scope, but we should be using the method scope.
+// // In practice this is no problem UNLESS the method has type parameters, such as <T> T[] toArray(T[] in).
+// // In this case the class scope cannot resolve the T[] parameter and erase it to Object[], which is a big problem because
+// // it would mean manually writing <X> X[] toArray(X[] in) does NOT stop lombok from ALSO trying to make the delegated toArray method,
+// // thus causing an error (2 methods with post-erasure duplicate signatures). Our 'fix' for this is to treat any method with type parameters
+// // as if each parameter's type matches anything else; so, if the name matches and the parameter count, we DONT generate it, even if its just
+// // an overloaded method.
+// //
+// // The reason we do this now is because making that MethodScope properly is effectively impossible at this step, so we need to do the resolving
+// // ourselves, which involves chasing down array bindings (T[]), following the path down type variables, i.e. <X extends Y, Y extends T>, and then
+// // resolving the final result of this exercise against the class scope.
+//
+// // When this crappy incomplete workaround of ours occurs, we end up in this else block, which does nothing and thus we fall through and remove
+// // the method.
+// }
+// it.remove(); // Method already exists in this class - don't create a delegating implementation.
+// }
+// }
+// }
+
+ private static void generateDelegateMethods(EclipseNode typeNode, List<BindingTuple> methods) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) typeNode.top().get();
- for (BindingPair pair : methods) {
- MethodDeclaration method = createDelegateMethod(delegate, typeNode, pair, top.compilationResult, annNode);
+ for (BindingTuple pair : methods) {
+ EclipseNode annNode = typeNode.getAst().get(pair.responsible);
+ MethodDeclaration method = createDelegateMethod(pair.fieldName, typeNode, pair, top.compilationResult, annNode);
if (method != null) EclipseHandlerUtil.injectMethod(typeNode, method);
}
}
@@ -194,7 +333,7 @@ public class PatchDelegate {
return false;
}
- public static void checkConflictOfTypeVarNames(BindingPair binding, EclipseNode typeNode) throws CantMakeDelegates {
+ public static void checkConflictOfTypeVarNames(BindingTuple binding, EclipseNode typeNode) throws CantMakeDelegates {
TypeVariableBinding[] typeVars = binding.parameterized.typeVariables();
if (typeVars == null || typeVars.length == 0) return;
@@ -320,7 +459,7 @@ public class PatchDelegate {
}
}
- private static MethodDeclaration createDelegateMethod(char[] name, EclipseNode typeNode, BindingPair pair, CompilationResult compilationResult, EclipseNode annNode) {
+ private static MethodDeclaration createDelegateMethod(char[] name, EclipseNode typeNode, BindingTuple pair, CompilationResult compilationResult, EclipseNode annNode) {
/* public <T, U, ...> ReturnType methodName(ParamType1 name1, ParamType2 name2, ...) throws T1, T2, ... {
* (return) delegate.<T, U>methodName(name1, name2);
* }
@@ -441,29 +580,28 @@ public class PatchDelegate {
}
private static final class Reflection {
- public static final Method classScopeBuildMethodsMethod;
+ public static final Method classScopeBuildFieldsAndMethodsMethod;
static {
Method m = null;
try {
- m = ClassScope.class.getDeclaredMethod("buildMethods");
+ m = ClassScope.class.getDeclaredMethod("buildFieldsAndMethods");
m.setAccessible(true);
} catch (Exception e) {
// That's problematic, but as long as no local classes are used we don't actually need it.
// Better fail on local classes than crash altogether.
}
- classScopeBuildMethodsMethod = m;
+ classScopeBuildFieldsAndMethodsMethod = m;
}
}
- private static void addAllMethodBindings(List<BindingPair> list, TypeBinding binding) {
- Set<String> banList = new HashSet<String>();
+ private static void addAllMethodBindings(List<BindingTuple> list, TypeBinding binding, Set<String> banList, char[] fieldName, ASTNode responsible) {
banList.addAll(METHODS_IN_OBJECT);
- addAllMethodBindings(list, binding, banList);
+ addAllMethodBindings0(list, binding, banList, fieldName, responsible);
}
- private static void addAllMethodBindings(List<BindingPair> list, TypeBinding binding, Set<String> banList) {
+ private static void addAllMethodBindings0(List<BindingTuple> list, TypeBinding binding, Set<String> banList, char[] fieldName, ASTNode responsible) {
if (binding == null) return;
TypeBinding inner;
@@ -478,7 +616,7 @@ public class PatchDelegate {
ClassScope cs = ((SourceTypeBinding)inner).scope;
if (cs != null) {
try {
- Reflection.classScopeBuildMethodsMethod.invoke(cs);
+ Reflection.classScopeBuildFieldsAndMethodsMethod.invoke(cs);
} catch (Exception e) {
// See 'Reflection' class for why we ignore this exception.
}
@@ -507,21 +645,32 @@ public class PatchDelegate {
if (!mb.isPublic()) continue;
if (mb.isSynthetic()) continue;
if (!banList.add(sig)) continue; // If add returns false, it was already in there.
- BindingPair pair = new BindingPair();
- pair.parameterized = mb;
- pair.base = baseSigs[i];
+ BindingTuple pair = new BindingTuple(mb, baseSigs[i], fieldName, responsible);
list.add(pair);
}
- addAllMethodBindings(list, rb.superclass(), banList);
+ addAllMethodBindings0(list, rb.superclass(), banList, fieldName, responsible);
ReferenceBinding[] interfaces = rb.superInterfaces();
if (interfaces != null) {
- for (ReferenceBinding iface : interfaces) addAllMethodBindings(list, iface, banList);
+ for (ReferenceBinding iface : interfaces) addAllMethodBindings0(list, iface, banList, fieldName, responsible);
}
}
}
- private static final class BindingPair {
- MethodBinding parameterized, base;
+ private static final class BindingTuple {
+ BindingTuple(MethodBinding parameterized, MethodBinding base, char[] fieldName, ASTNode responsible) {
+ this.parameterized = parameterized;
+ this.base = base;
+ this.fieldName = fieldName;
+ this.responsible = responsible;
+ }
+
+ final MethodBinding parameterized, base;
+ final char[] fieldName;
+ final ASTNode responsible;
+
+ @Override public String toString() {
+ return String.format("{param: %s, base: %s, fieldName: %s}", parameterized == null ? "(null)" : printSig(parameterized), base == null ? "(null)" : printSig(base), new String(fieldName));
+ }
}
private static final List<String> METHODS_IN_OBJECT = Collections.unmodifiableList(Arrays.asList(