diff options
-rw-r--r-- | src/core/lombok/Delegate.java | 2 | ||||
-rw-r--r-- | src/core/lombok/eclipse/Eclipse.java | 2 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java | 2 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java | 11 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java | 223 |
5 files changed, 237 insertions, 3 deletions
diff --git a/src/core/lombok/Delegate.java b/src/core/lombok/Delegate.java index 0bbaed0d..bbfd51df 100644 --- a/src/core/lombok/Delegate.java +++ b/src/core/lombok/Delegate.java @@ -22,5 +22,5 @@ package lombok; public @interface Delegate { - Class<?> value() default java.lang.Object.class; + Class<?>[] value() default {}; } diff --git a/src/core/lombok/eclipse/Eclipse.java b/src/core/lombok/eclipse/Eclipse.java index 8354215b..4101370c 100644 --- a/src/core/lombok/eclipse/Eclipse.java +++ b/src/core/lombok/eclipse/Eclipse.java @@ -299,7 +299,7 @@ public class Eclipse { return ref; } - private static long pos(ASTNode node) { + public static long pos(ASTNode node) { return ((long) node.sourceStart << 32) | (node.sourceEnd & 0xFFFFFFFFL); } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index f252793d..921d955e 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -506,7 +506,7 @@ public class EclipseHandlerUtil { private static final char[] ALL = "all".toCharArray(); - private static Annotation[] createSuppressWarningsAll(ASTNode source, Annotation[] originalAnnotationArray) { + public static Annotation[] createSuppressWarningsAll(ASTNode source, Annotation[] originalAnnotationArray) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; long[] poss = new long[3]; diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 399ea357..c54ef604 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -257,9 +257,20 @@ public class EclipsePatcher extends Agent { } private static void patchEcjTransformers(ScriptManager sm, boolean ecj) { + patchDelegate(sm); patchHandleVal(sm, ecj); } + private static void patchDelegate(ScriptManager sm) { + final String TYPEDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration"; + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(TYPEDECLARATION_SIG, "resolve", "void")) + .request(StackRequest.THIS) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleDelegateForType", "boolean", TYPEDECLARATION_SIG)) + .build()); + } + // Creates a copy of the 'initialization' field on a LocalDeclaration if the type of the LocalDeclaration is 'val', because the completion parser will null this out, // which in turn stops us from inferring the intended type for 'val x = 5;'. We look at the copy. // Also patches local declaration to not call .resolveType() on the initializer expression if we've already done so (calling it twice causes weird errors), diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index e2a52929..e71caa3d 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -22,33 +22,60 @@ package lombok.eclipse.agent; +import static lombok.eclipse.Eclipse.*; + import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import lombok.core.DiagnosticsReceiver; import lombok.core.PostCompiler; +import lombok.core.AST.Kind; import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; +import org.eclipse.jdt.internal.compiler.ast.Clinit; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; @@ -319,4 +346,200 @@ public class PatchFixes { if (decl != null && LocalDeclaration.class.equals(decl.getClass()) && expr.resolvedType != null) return expr.resolvedType; return expr.resolveType(scope); } + + public static boolean handleDelegateForType(TypeDeclaration decl) { + if (decl.scope == null) return false; + if (decl.fields == null) return false; + + for (FieldDeclaration field : decl.fields) { + if (field.annotations == null) continue; + for (Annotation ann : field.annotations) { + if (ann.type == null) continue; + TypeBinding tb = ann.type.resolveType(decl.initializerScope); + if (!charArrayEquals("lombok", tb.qualifiedPackageName())) continue; + if (!charArrayEquals("Delegate", tb.qualifiedSourceName())) continue; + + List<ClassLiteralAccess> rawTypes = new ArrayList<ClassLiteralAccess>(); + for (MemberValuePair pair : ann.memberValuePairs()) { + if (pair.name == null || charArrayEquals("value", pair.name)) { + if (pair.value instanceof ArrayInitializer) { + for (Expression expr : ((ArrayInitializer)pair.value).expressions) { + if (expr instanceof ClassLiteralAccess) rawTypes.add((ClassLiteralAccess) expr); + } + } + if (pair.value instanceof ClassLiteralAccess) { + rawTypes.add((ClassLiteralAccess) pair.value); + } + } + } + + List<MethodBinding> methodsToDelegate = new ArrayList<MethodBinding>(); + + if (rawTypes.isEmpty()) { + addAllMethodBindings(methodsToDelegate, field.type.resolveType(decl.initializerScope)); + } else { + for (ClassLiteralAccess cla : rawTypes) { + addAllMethodBindings(methodsToDelegate, cla.type.resolveType(decl.initializerScope)); + } + } + + System.out.println("About to generate the following methods, all delegating to: this." + new String(field.name)); + for (MethodBinding mb : methodsToDelegate) { + System.out.println(mb); + } + System.out.println("-----------"); + + generateDelegateMethods(decl, methodsToDelegate, field.name, ann); + } + } + + return false; + } + + private static final Method methodScopeCreateMethodMethod; + private static final Field sourceTypeBindingMethodsField; + + static { + Method m = null; + Field f = null; + Exception ex = null; + + try { + m = MethodScope.class.getDeclaredMethod("createMethod", AbstractMethodDeclaration.class); + m.setAccessible(true); + f = SourceTypeBinding.class.getDeclaredField("methods"); + f.setAccessible(true); + } catch (Exception e) { + ex = e; + } + + methodScopeCreateMethodMethod = m; + sourceTypeBindingMethodsField = f; + if (ex != null) throw new RuntimeException(ex); + } + + private static void generateDelegateMethods(TypeDeclaration type, List<MethodBinding> methods, char[] delegate, ASTNode source) { + for (MethodBinding binding : methods) { + MethodDeclaration method = generateDelegateMethod(delegate, binding, type.compilationResult, source); + if (type.methods == null) { + type.methods = new AbstractMethodDeclaration[1]; + type.methods[0] = method; + } else { + int insertionPoint; + for (insertionPoint = 0; insertionPoint < type.methods.length; insertionPoint++) { + AbstractMethodDeclaration current = type.methods[insertionPoint]; + if (current instanceof Clinit) continue; + if (Eclipse.isGenerated(current)) continue; + break; + } + AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1]; + System.arraycopy(type.methods, 0, newArray, 0, insertionPoint); + if (insertionPoint <= type.methods.length) { + System.arraycopy(type.methods, insertionPoint, newArray, insertionPoint + 1, type.methods.length - insertionPoint); + } + + newArray[insertionPoint] = method; + type.methods = newArray; + MethodScope methodScope = new MethodScope(type.scope, method, false); + + try { + MethodBinding methodBinding = (MethodBinding) methodScopeCreateMethodMethod.invoke(methodScope, method); + System.out.println("SCOPE NOW: " + method.scope); + + method.resolve(type.scope); + System.out.println("Bind now: " + methodBinding.returnType); + + MethodBinding[] existing = (MethodBinding[]) sourceTypeBindingMethodsField.get(type.binding); + if (existing == null) existing = new MethodBinding[] {methodBinding}; + else { + MethodBinding[] copy = new MethodBinding[existing.length + 1]; + System.arraycopy(existing, 0, copy, 0, existing.length); + copy[existing.length] = methodBinding; + } + sourceTypeBindingMethodsField.set(type.binding, existing); + System.out.println("Added method binding: " + methodBinding); + System.out.println(method); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + + private static MethodDeclaration generateDelegateMethod(char[] name, MethodBinding binding, CompilationResult compilationResult, ASTNode source) { + MethodDeclaration method = new MethodDeclaration(compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = ClassFileConstants.AccPublic; + method.returnType = Eclipse.makeType(binding.returnType, source, false); + method.annotations = EclipseHandlerUtil.createSuppressWarningsAll(source, null); + if (binding.parameters != null && binding.parameters.length > 0) { + method.arguments = new Argument[binding.parameters.length]; + for (int i = 0; i < method.arguments.length; i++) { + String argName = "$p" + i; + method.arguments[i] = new Argument( + argName.toCharArray(), pos(source), + Eclipse.makeType(binding.parameters[i], source, false), + ClassFileConstants.AccFinal); + } + } + method.selector = binding.selector; + if (binding.thrownExceptions != null && binding.thrownExceptions.length > 0) { + method.thrownExceptions = new TypeReference[binding.thrownExceptions.length]; + for (int i = 0; i < method.thrownExceptions.length; i++) { + method.thrownExceptions[i] = Eclipse.makeType(binding.thrownExceptions[i], source, false); + } + } + + method.typeParameters = null; // TODO think about this + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldReference fieldRef = new FieldReference(name, pos(source)); + fieldRef.receiver = new ThisReference(source.sourceStart, source.sourceEnd); + MessageSend call = new MessageSend(); + call.receiver = fieldRef; + call.selector = binding.selector; + if (method.arguments != null) { + call.arguments = new Expression[method.arguments.length]; + for (int i = 0; i < method.arguments.length; i++) { + call.arguments[i] = new SingleNameReference(("$p" + i).toCharArray(), pos(source)); + } + } + + Statement body; + if (method.returnType instanceof SingleTypeReference && ((SingleTypeReference)method.returnType).token == TypeConstants.VOID) { + body = call; + } else { + body = new ReturnStatement(call, source.sourceStart, source.sourceEnd); + } + + method.statements = new Statement[] {body}; + // TODO add Eclipse.setGeneratedBy everywhere. + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + return method; + } + + private static void addAllMethodBindings(List<MethodBinding> list, TypeBinding binding) { + if (binding instanceof ReferenceBinding) { + for (MethodBinding mb : ((ReferenceBinding)binding).availableMethods()) { + if (mb.isStatic()) continue; + if (mb.isBridge()) continue; + if (mb.isConstructor()) continue; + if (mb.isDefaultAbstract()) continue; + if (!mb.isPublic()) continue; + if (mb.isSynthetic()) continue; + list.add(mb); + } + } + } + + private static boolean charArrayEquals(String s, char[] c) { + if (s == null) return c == null; + if (c == null) return false; + + if (s.length() != c.length) return false; + for (int i = 0; i < s.length(); i++) if (s.charAt(i) != c[i]) return false; + return true; + + + } } |