aboutsummaryrefslogtreecommitdiff
path: root/src/eclipseAgent/lombok/eclipse/agent
diff options
context:
space:
mode:
Diffstat (limited to 'src/eclipseAgent/lombok/eclipse/agent')
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java46
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/ExtensionMethodCompletionProposal.java99
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/PatchDelegatePortal.java67
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java471
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodPortal.java133
5 files changed, 815 insertions, 1 deletions
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
index c14710c8..380b961e 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
@@ -21,6 +21,8 @@
*/
package lombok.eclipse.agent;
+import static lombok.patcher.scripts.ScriptBuilder.*;
+
import java.lang.instrument.Instrumentation;
import java.util.Collection;
import java.util.Collections;
@@ -101,6 +103,7 @@ public class EclipsePatcher extends Agent {
patchAvoidReparsingGeneratedCode(sm);
patchLombokizeAST(sm);
patchEcjTransformers(sm, ecjOnly);
+ patchExtensionMethod(sm, ecjOnly);
if (reloadExistingClasses) sm.reloadClasses(instrumentation);
}
@@ -546,7 +549,7 @@ public class EclipsePatcher extends Agent {
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void"))
.request(StackRequest.THIS)
- .decisionMethod(new Hook(PatchDelegate.class.getName(), "handleDelegateForType", "boolean", CLASSSCOPE_SIG))
+ .decisionMethod(new Hook("lombok.eclipse.agent.PatchDelegatePortal", "handleDelegateForType", "boolean", "java.lang.Object"))
.build());
}
@@ -641,4 +644,45 @@ public class EclipsePatcher extends Agent {
.wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG))
.request(StackRequest.PARAM1, StackRequest.RETURN_VALUE).build());
}
+
+ private static void patchExtensionMethod(ScriptManager sm, boolean ecj) {
+ final String PATCH_EXTENSIONMETHOD_PORTAL = "lombok.eclipse.agent.PatchExtensionMethodPortal";
+ final String MESSAGE_SEND_SIG = "org.eclipse.jdt.internal.compiler.ast.MessageSend";
+ final String TYPE_BINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding";
+ final String BLOCK_SCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope";
+ final String TYPE_BINDINGS_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding[]";
+ final String PROBLEM_REPORTER_SIG = "org.eclipse.jdt.internal.compiler.problem.ProblemReporter";
+ final String METHOD_BINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.MethodBinding";
+ final String COMPLETION_PROPOSAL_COLLECTOR_SIG = "org.eclipse.jdt.ui.text.java.CompletionProposalCollector";
+ final String I_JAVA_COMPLETION_PROPOSAL_SIG = "org.eclipse.jdt.ui.text.java.IJavaCompletionProposal[]";
+
+ sm.addScript(wrapReturnValue()
+ .target(new MethodTarget(MESSAGE_SEND_SIG, "resolveType", TYPE_BINDING_SIG, BLOCK_SCOPE_SIG))
+ .request(StackRequest.RETURN_VALUE)
+ .request(StackRequest.THIS)
+ .request(StackRequest.PARAM1)
+ .wrapMethod(new Hook(PATCH_EXTENSIONMETHOD_PORTAL, "resolveType", TYPE_BINDING_SIG, "java.lang.Object", "java.lang.Object", "java.lang.Object"))
+ .build());
+
+ sm.addScript(replaceMethodCall()
+ .target(new MethodTarget(MESSAGE_SEND_SIG, "resolveType", TYPE_BINDING_SIG, BLOCK_SCOPE_SIG))
+ .methodToReplace(new Hook(PROBLEM_REPORTER_SIG, "errorNoMethodFor", "void", MESSAGE_SEND_SIG, TYPE_BINDING_SIG, TYPE_BINDINGS_SIG))
+ .replacementMethod(new Hook(PATCH_EXTENSIONMETHOD_PORTAL, "errorNoMethodFor", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object", "java.lang.Object"))
+ .build());
+ sm.addScript(replaceMethodCall()
+ .target(new MethodTarget(MESSAGE_SEND_SIG, "resolveType", TYPE_BINDING_SIG, BLOCK_SCOPE_SIG))
+ .methodToReplace(new Hook(PROBLEM_REPORTER_SIG, "invalidMethod", "void", MESSAGE_SEND_SIG, METHOD_BINDING_SIG))
+ .replacementMethod(new Hook(PATCH_EXTENSIONMETHOD_PORTAL, "invalidMethod", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object"))
+ .build());
+
+ if (!ecj) {
+ sm.addScript(wrapReturnValue()
+ .target(new MethodTarget(COMPLETION_PROPOSAL_COLLECTOR_SIG, "getJavaCompletionProposals", I_JAVA_COMPLETION_PROPOSAL_SIG))
+ .request(StackRequest.RETURN_VALUE)
+ .request(StackRequest.THIS)
+// .wrapMethod(new Hook(PATCH_EXTENSIONMETHOD_PORTAL, "getJavaCompletionProposals", I_JAVA_COMPLETION_PROPOSAL_SIG, I_JAVA_COMPLETION_PROPOSAL_SIG, COMPLETION_PROPOSAL_COLLECTOR_SIG))
+ .wrapMethod(new Hook(PATCH_EXTENSIONMETHOD_PORTAL, "getJavaCompletionProposals", "java.lang.Object", "java.lang.Object", "java.lang.Object"))
+ .build());
+ }
+ }
}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/ExtensionMethodCompletionProposal.java b/src/eclipseAgent/lombok/eclipse/agent/ExtensionMethodCompletionProposal.java
new file mode 100644
index 00000000..46ce63f9
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/ExtensionMethodCompletionProposal.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.agent;
+
+import static org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants.AccStatic;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.core.CompletionProposal;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.codeassist.CompletionEngine;
+import org.eclipse.jdt.internal.codeassist.InternalCompletionProposal;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMemberAccess;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedNameReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+
+public class ExtensionMethodCompletionProposal extends InternalCompletionProposal {
+ public ExtensionMethodCompletionProposal(final int replacementOffset) {
+ super(CompletionProposal.METHOD_REF, replacementOffset - 1);
+ }
+
+ public void setMethodBinding(final MethodBinding method, final ASTNode node) {
+ MethodBinding original = method.original();
+ TypeBinding[] parameters = Arrays.copyOf(method.parameters, method.parameters.length);
+ method.parameters = Arrays.copyOfRange(method.parameters, 1, method.parameters.length);
+ TypeBinding[] originalParameters = null;
+ if (original != method) {
+ originalParameters = Arrays.copyOf(method.original().parameters, method.original().parameters.length);
+ method.original().parameters = Arrays.copyOfRange(method.original().parameters, 1, method.original().parameters.length);
+ }
+
+ int length = method.parameters == null ? 0 : method.parameters.length;
+ char[][] parameterPackageNames = new char[length][];
+ char[][] parameterTypeNames = new char[length][];
+
+ for (int i = 0; i < length; i++) {
+ TypeBinding type = method.original().parameters[i];
+ parameterPackageNames[i] = type.qualifiedPackageName();
+ parameterTypeNames[i] = type.qualifiedSourceName();
+ }
+ char[] completion = CharOperation.concat(method.selector, new char[] { '(', ')' });
+ setDeclarationSignature(CompletionEngine.getSignature(method.declaringClass));
+ setSignature(CompletionEngine.getSignature(method));
+
+ if (original != method) {
+ setOriginalSignature(CompletionEngine.getSignature(original));
+ }
+ setDeclarationPackageName(method.declaringClass.qualifiedPackageName());
+ setDeclarationTypeName(method.declaringClass.qualifiedSourceName());
+ setParameterPackageNames(parameterPackageNames);
+ setParameterTypeNames(parameterTypeNames);
+ setPackageName(method.returnType.qualifiedPackageName());
+ setTypeName(method.returnType.qualifiedSourceName());
+ setName(method.selector);
+ setCompletion(completion);
+ setFlags(method.modifiers & (~AccStatic));
+ int index = node.sourceEnd + 1;
+ if (node instanceof CompletionOnQualifiedNameReference) {
+ index -= ((CompletionOnQualifiedNameReference) node).completionIdentifier.length;
+ }
+ if (node instanceof CompletionOnMemberAccess) {
+ index -= ((CompletionOnMemberAccess) node).token.length;
+ }
+ if (node instanceof CompletionOnSingleNameReference) {
+ index -= ((CompletionOnSingleNameReference) node).token.length;
+ }
+ setReplaceRange(index, index);
+ setTokenRange(index, index);
+
+ setRelevance(100);
+
+ method.parameters = parameters;
+ if (original != method) {
+ method.original().parameters = originalParameters;
+ }
+ }
+}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegatePortal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegatePortal.java
new file mode 100644
index 00000000..1620014c
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegatePortal.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.agent;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import lombok.Lombok;
+
+public class PatchDelegatePortal {
+ static final String CLASS_SCOPE = "org.eclipse.jdt.internal.compiler.lookup.ClassScope";
+
+ public static boolean handleDelegateForType(Object classScope) {
+ try {
+ return (Boolean) Reflection.handleDelegateForType.invoke(null, classScope);
+ } catch (NoClassDefFoundError e) {
+ //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
+ //do anything useful here.
+ return false;
+ } catch (IllegalAccessException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (InvocationTargetException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (NullPointerException e) {
+ e.initCause(Reflection.problem);
+ throw e;
+ }
+ }
+
+ private static final class Reflection {
+ public static final Method handleDelegateForType;
+ public static final Throwable problem;
+
+ static {
+ Method m = null;
+ Throwable problem_ = null;
+ try {
+ m = PatchDelegate.class.getMethod("handleDelegateForType", Class.forName(CLASS_SCOPE));
+ } catch (Throwable t) {
+ // 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.
+ problem_ = t;
+ }
+ handleDelegateForType = m;
+ problem = problem_;
+ }
+ }
+}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
new file mode 100644
index 00000000..589f791c
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.agent;
+
+import static lombok.eclipse.handlers.EclipseHandlerUtil.createAnnotation;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.core.AnnotationValues.AnnotationValueDecodeFail;
+import lombok.eclipse.EclipseAST;
+import lombok.eclipse.EclipseNode;
+import lombok.eclipse.TransformEclipseAST;
+import lombok.eclipse.handlers.EclipseHandlerUtil;
+import lombok.experimental.ExtensionMethod;
+
+import org.eclipse.jdt.core.CompletionProposal;
+import org.eclipse.jdt.internal.codeassist.InternalCompletionContext;
+import org.eclipse.jdt.internal.codeassist.InternalCompletionProposal;
+import org.eclipse.jdt.internal.codeassist.InternalExtendedCompletionContext;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMemberAccess;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedNameReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+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.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+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.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
+import org.eclipse.jdt.internal.core.SearchableEnvironment;
+import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.CompletionProposalCollector;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+
+public class PatchExtensionMethod {
+ private static class Extension {
+ List<MethodBinding> extensionMethods;
+ boolean suppressBaseMethods;
+ }
+
+ private static class PostponedNoMethodError implements PostponedError {
+ private final ProblemReporter problemReporter;
+ private final MessageSend messageSend;
+ private final TypeBinding recType;
+ private final TypeBinding[] params;
+
+ PostponedNoMethodError(ProblemReporter problemReporter, MessageSend messageSend, TypeBinding recType, TypeBinding[] params) {
+ this.problemReporter = problemReporter;
+ this.messageSend = messageSend;
+ this.recType = recType;
+ this.params = params;
+ }
+
+ public void fire() {
+ problemReporter.errorNoMethodFor(messageSend, recType, params);
+ }
+ }
+
+ private static class PostponedInvalidMethodError implements PostponedError {
+ private final ProblemReporter problemReporter;
+ private final MessageSend messageSend;
+ private final MethodBinding method;
+
+ PostponedInvalidMethodError(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) {
+ this.problemReporter = problemReporter;
+ this.messageSend = messageSend;
+ this.method = method;
+ }
+
+ public void fire() {
+ problemReporter.invalidMethod(messageSend, method);
+ }
+ }
+
+ private static interface PostponedError {
+ public void fire();
+ }
+
+ public static EclipseNode getTypeNode(TypeDeclaration decl) {
+ CompilationUnitDeclaration cud = decl.scope.compilationUnitScope().referenceContext;
+ EclipseAST astNode = TransformEclipseAST.getAST(cud, false);
+ EclipseNode node = astNode.get(decl);
+ if (node == null) {
+ astNode = TransformEclipseAST.getAST(cud, true);
+ node = astNode.get(decl);
+ }
+ return node;
+ }
+
+ public static Annotation getAnnotation(Class<? extends java.lang.annotation.Annotation> expectedType, EclipseNode node) {
+ TypeDeclaration decl = (TypeDeclaration) node.get();
+ if (decl.annotations != null) for (Annotation ann : decl.annotations) {
+ if (EclipseHandlerUtil.typeMatches(expectedType, node, ann.type)) return ann;
+ }
+ return null;
+ }
+
+ private static EclipseNode upToType(EclipseNode typeNode) {
+ EclipseNode node = typeNode;
+ do {
+ node = node.up();
+ } while ((node != null) && (node.getKind() != Kind.TYPE));
+ return node;
+ }
+
+ private static List<Extension> getApplicableExtensionMethods(EclipseNode typeNode, Annotation ann, TypeBinding receiverType) {
+ List<Extension> extensions = new ArrayList<Extension>();
+ if ((typeNode != null) && (ann != null) && (receiverType != null)) {
+ BlockScope blockScope = ((TypeDeclaration) typeNode.get()).initializerScope;
+ EclipseNode annotationNode = typeNode.getNodeFor(ann);
+ AnnotationValues<ExtensionMethod> annotation = createAnnotation(ExtensionMethod.class, annotationNode);
+ boolean suppressBaseMethods = false;
+ try {
+ suppressBaseMethods = annotation.getInstance().suppressBaseMethods();
+ } catch (AnnotationValueDecodeFail fail) {
+ fail.owner.setError(fail.getMessage(), fail.idx);
+ }
+ for (Object extensionMethodProvider : annotation.getActualExpressions("value")) {
+ if (extensionMethodProvider instanceof ClassLiteralAccess) {
+ TypeBinding binding = ((ClassLiteralAccess) extensionMethodProvider).type.resolveType(blockScope);
+ if (binding == null) continue;
+ if (!binding.isClass() && !binding.isEnum()) continue;
+ Extension e = new Extension();
+ e.extensionMethods = getApplicableExtensionMethodsDefinedInProvider(typeNode, (ReferenceBinding) binding, receiverType);
+ e.suppressBaseMethods = suppressBaseMethods;
+ extensions.add(e);
+ }
+ }
+ }
+ return extensions;
+ }
+
+ private static List<MethodBinding> getApplicableExtensionMethodsDefinedInProvider(EclipseNode typeNode, ReferenceBinding extensionMethodProviderBinding,
+ TypeBinding receiverType) {
+
+ List<MethodBinding> extensionMethods = new ArrayList<MethodBinding>();
+ CompilationUnitScope cuScope = ((CompilationUnitDeclaration) typeNode.top().get()).scope;
+ for (MethodBinding method : extensionMethodProviderBinding.methods()) {
+ if (!method.isStatic()) continue;
+ if (!method.isPublic()) continue;
+ if (method.parameters == null || method.parameters.length == 0) continue;
+ TypeBinding firstArgType = method.parameters[0];
+ if (receiverType.isProvablyDistinct(firstArgType) && !receiverType.isCompatibleWith(firstArgType.erasure())) continue;
+ TypeBinding[] argumentTypes = Arrays.copyOfRange(method.parameters, 1, method.parameters.length);
+ if ((receiverType instanceof ReferenceBinding) && ((ReferenceBinding) receiverType).getExactMethod(method.selector, argumentTypes, cuScope) != null) continue;
+ extensionMethods.add(method);
+ }
+ return extensionMethods;
+ }
+
+ private static final Map<MessageSend, PostponedError> ERRORS = new WeakHashMap<MessageSend, PostponedError>();
+
+ public static void errorNoMethodFor(ProblemReporter problemReporter, MessageSend messageSend, TypeBinding recType, TypeBinding[] params) {
+ ERRORS.put(messageSend, new PostponedNoMethodError(problemReporter, messageSend, recType, params));
+ }
+
+ public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) {
+ ERRORS.put(messageSend, new PostponedInvalidMethodError(problemReporter, messageSend, method));
+ }
+
+ public static TypeBinding resolveType(TypeBinding resolvedType, MessageSend methodCall, BlockScope scope) {
+ List<Extension> extensions = new ArrayList<Extension>();
+ TypeDeclaration decl = scope.classScope().referenceContext;
+
+ EclipseNode owningType = null;
+
+ for (EclipseNode typeNode = getTypeNode(decl); typeNode != null; typeNode = upToType(typeNode)) {
+ Annotation ann = getAnnotation(ExtensionMethod.class, typeNode);
+ if (ann != null) {
+ extensions.addAll(0, getApplicableExtensionMethods(typeNode, ann, methodCall.receiver.resolvedType));
+ if (owningType == null) owningType = typeNode;
+ }
+ }
+
+ if (methodCall.binding == null) return resolvedType;
+
+ for (Extension extension : extensions) {
+ if (!extension.suppressBaseMethods && !(methodCall.binding instanceof ProblemMethodBinding)) continue;
+ for (MethodBinding extensionMethod : extension.extensionMethods) {
+ if (!Arrays.equals(methodCall.selector, extensionMethod.selector)) continue;
+ ERRORS.remove(methodCall);
+ if (methodCall.receiver instanceof ThisReference) {
+ methodCall.receiver.bits &= ~ASTNode.IsImplicitThis;
+ }
+ List<Expression> arguments = new ArrayList<Expression>();
+ arguments.add(methodCall.receiver);
+ if (methodCall.arguments != null) arguments.addAll(Arrays.asList(methodCall.arguments));
+ List<TypeBinding> argumentTypes = new ArrayList<TypeBinding>();
+ argumentTypes.add(methodCall.receiver.resolvedType);
+ if (methodCall.binding.parameters != null) argumentTypes.addAll(Arrays.asList(methodCall.binding.parameters));
+ MethodBinding fixedBinding = scope.getMethod(extensionMethod.declaringClass, methodCall.selector, argumentTypes.toArray(new TypeBinding[0]), methodCall);
+ if (fixedBinding instanceof ProblemMethodBinding) {
+ if (fixedBinding.declaringClass != null) {
+ scope.problemReporter().invalidMethod(methodCall, fixedBinding);
+ }
+ } else {
+ for (int i = 0, iend = arguments.size(); i < iend; i++) {
+ Expression arg = arguments.get(i);
+ if (fixedBinding.parameters[i].isArrayType() != arg.resolvedType.isArrayType()) break;
+ if (arg.resolvedType.isArrayType()) {
+ if (arg instanceof MessageSend) {
+ ((MessageSend) arg).valueCast = arg.resolvedType;
+ }
+ }
+ if (!fixedBinding.parameters[i].isBaseType() && arg.resolvedType.isBaseType()) {
+ int id = arg.resolvedType.id;
+ arg.implicitConversion = TypeIds.BOXING | (id + (id << 4)); // magic see TypeIds
+ } else if (fixedBinding.parameters[i].isBaseType() && !arg.resolvedType.isBaseType()) {
+ int id = fixedBinding.parameters[i].id;
+ arg.implicitConversion = TypeIds.UNBOXING | (id + (id << 4)); // magic see TypeIds
+ }
+ }
+ methodCall.arguments = arguments.toArray(new Expression[0]);
+
+ methodCall.receiver = createNameRef(extensionMethod.declaringClass, methodCall);
+ methodCall.actualReceiverType = extensionMethod.declaringClass;
+ methodCall.binding = fixedBinding;
+ methodCall.resolvedType = methodCall.binding.returnType;
+ }
+ return methodCall.resolvedType;
+ }
+ }
+
+ PostponedError error = ERRORS.get(methodCall);
+ if (error != null) error.fire();
+
+ ERRORS.remove(methodCall);
+ return resolvedType;
+ }
+
+ private static NameReference createNameRef(TypeBinding typeBinding, ASTNode source) {
+ long p = ((long) source.sourceStart << 32) | source.sourceEnd;
+ char[] pkg = typeBinding.qualifiedPackageName();
+ char[] basename = typeBinding.qualifiedSourceName();
+
+ StringBuilder sb = new StringBuilder();
+ if (pkg != null) sb.append(pkg);
+ if (sb.length() > 0) sb.append(".");
+ sb.append(basename);
+
+ String tName = sb.toString();
+
+ if (tName.indexOf('.') == -1) {
+ return new SingleNameReference(basename, p);
+ } else {
+ char[][] sources;
+ String[] in = tName.split("\\.");
+ sources = new char[in.length][];
+ for (int i = 0; i < in.length; i++) sources[i] = in[i].toCharArray();
+ long[] poss = new long[in.length];
+ Arrays.fill(poss, p);
+ return new QualifiedNameReference(sources, poss, source.sourceStart, source.sourceEnd);
+ }
+ }
+
+ public static IJavaCompletionProposal[] getJavaCompletionProposals(IJavaCompletionProposal[] javaCompletionProposals,
+ CompletionProposalCollector completionProposalCollector) {
+
+ List<IJavaCompletionProposal> proposals = new ArrayList<IJavaCompletionProposal>(Arrays.asList(javaCompletionProposals));
+ if (canExtendCodeAssist(proposals)) {
+ IJavaCompletionProposal firstProposal = proposals.get(0);
+ int replacementOffset = getReplacementOffset(firstProposal);
+ for (Extension extension : getExtensionMethods(completionProposalCollector)) {
+ for (MethodBinding method : extension.extensionMethods) {
+ ExtensionMethodCompletionProposal newProposal = new ExtensionMethodCompletionProposal(replacementOffset);
+ copyNameLookupAndCompletionEngine(completionProposalCollector, firstProposal, newProposal);
+ ASTNode node = getAssistNode(completionProposalCollector);
+ newProposal.setMethodBinding(method, node);
+ createAndAddJavaCompletionProposal(completionProposalCollector, newProposal, proposals);
+ }
+ }
+ }
+ return proposals.toArray(new IJavaCompletionProposal[proposals.size()]);
+ }
+
+ private static void copyNameLookupAndCompletionEngine(CompletionProposalCollector completionProposalCollector, IJavaCompletionProposal proposal,
+ InternalCompletionProposal newProposal) {
+
+ try {
+ InternalCompletionContext context = (InternalCompletionContext) Reflection.contextField.get(completionProposalCollector);
+ InternalExtendedCompletionContext extendedContext = (InternalExtendedCompletionContext) Reflection.extendedContextField.get(context);
+ LookupEnvironment lookupEnvironment = (LookupEnvironment) Reflection.lookupEnvironmentField.get(extendedContext);
+ Reflection.nameLookupField.set(newProposal, ((SearchableEnvironment) lookupEnvironment.nameEnvironment).nameLookup);
+ Reflection.completionEngineField.set(newProposal, lookupEnvironment.typeRequestor);
+ } catch (IllegalAccessException ignore) {
+ // ignore
+ }
+ }
+
+ private static void createAndAddJavaCompletionProposal(CompletionProposalCollector completionProposalCollector, CompletionProposal newProposal,
+ List<IJavaCompletionProposal> proposals) {
+
+ try {
+ proposals.add((IJavaCompletionProposal) Reflection.createJavaCompletionProposalMethod.invoke(completionProposalCollector, newProposal));
+ } catch (Exception ignore) {
+ // ignore
+ }
+ }
+
+ private static boolean canExtendCodeAssist(List<IJavaCompletionProposal> proposals) {
+ return !proposals.isEmpty() && Reflection.isComplete();
+ }
+
+ private static int getReplacementOffset(IJavaCompletionProposal proposal) {
+ try {
+ return Reflection.replacementOffsetField.getInt(proposal);
+ } catch (Exception ignore) {
+ return 0;
+ }
+ }
+
+ private static List<Extension> getExtensionMethods(CompletionProposalCollector completionProposalCollector) {
+ List<Extension> extensions = new ArrayList<Extension>();
+ ClassScope classScope = getClassScope(completionProposalCollector);
+ if (classScope != null) {
+ TypeDeclaration decl = classScope.referenceContext;
+ TypeBinding firstParameterType = getFirstParameterType(decl, completionProposalCollector);
+ for (EclipseNode typeNode = getTypeNode(decl); typeNode != null; typeNode = upToType(typeNode)) {
+ Annotation ann = getAnnotation(ExtensionMethod.class, typeNode);
+ extensions.addAll(0, getApplicableExtensionMethods(typeNode, ann, firstParameterType));
+ }
+ }
+ return extensions;
+ }
+
+ private static ClassScope getClassScope(CompletionProposalCollector completionProposalCollector) {
+ ClassScope scope = null;
+ try {
+ InternalCompletionContext context = (InternalCompletionContext) Reflection.contextField.get(completionProposalCollector);
+ InternalExtendedCompletionContext extendedContext = (InternalExtendedCompletionContext) Reflection.extendedContextField.get(context);
+ if (extendedContext != null) {
+ Scope assistScope = ((Scope) Reflection.assistScopeField.get(extendedContext));
+ if (assistScope != null) {
+ scope = assistScope.classScope();
+ }
+ }
+ } catch (IllegalAccessException ignore) {
+ // ignore
+ }
+ return scope;
+ }
+
+ private static TypeBinding getFirstParameterType(TypeDeclaration decl, CompletionProposalCollector completionProposalCollector) {
+ TypeBinding firstParameterType = null;
+ ASTNode node = getAssistNode(completionProposalCollector);
+ if (node == null) return null;
+ if (!(node instanceof CompletionOnQualifiedNameReference) && !(node instanceof CompletionOnSingleNameReference) && !(node instanceof CompletionOnMemberAccess)) return null;
+
+ if (node instanceof NameReference) {
+ Binding binding = ((NameReference) node).binding;
+ if ((node instanceof SingleNameReference) && (((SingleNameReference) node).token.length == 0)) {
+ firstParameterType = decl.binding;
+ } else if (binding instanceof VariableBinding) {
+ firstParameterType = ((VariableBinding) binding).type;
+ } else if (binding instanceof TypeBinding) {
+ firstParameterType = (TypeBinding) binding;
+ }
+ } else if (node instanceof FieldReference) {
+ firstParameterType = ((FieldReference) node).actualReceiverType;
+ }
+ return firstParameterType;
+ }
+
+ private static ASTNode getAssistNode(CompletionProposalCollector completionProposalCollector) {
+ try {
+ InternalCompletionContext context = (InternalCompletionContext) Reflection.contextField.get(completionProposalCollector);
+ InternalExtendedCompletionContext extendedContext = (InternalExtendedCompletionContext) Reflection.extendedContextField.get(context);
+ if (extendedContext == null) return null;
+ return (ASTNode) Reflection.assistNodeField.get(extendedContext);
+ } catch (Exception ignore) {
+ return null;
+ }
+ }
+
+ private static class Reflection {
+ public static final Field replacementOffsetField;
+ public static final Field contextField;
+ public static final Field extendedContextField;
+ public static final Field assistNodeField;
+ public static final Field assistScopeField;
+ public static final Field lookupEnvironmentField;
+ public static final Field completionEngineField;
+ public static final Field nameLookupField;
+ public static final Method createJavaCompletionProposalMethod;
+
+ static {
+ replacementOffsetField = accessField(AbstractJavaCompletionProposal.class, "fReplacementOffset");
+ contextField = accessField(CompletionProposalCollector.class, "fContext");
+ extendedContextField = accessField(InternalCompletionContext.class, "extendedContext");
+ assistNodeField = accessField(InternalExtendedCompletionContext.class, "assistNode");
+ assistScopeField = accessField(InternalExtendedCompletionContext.class, "assistScope");
+ lookupEnvironmentField = accessField(InternalExtendedCompletionContext.class, "lookupEnvironment");
+ completionEngineField = accessField(InternalCompletionProposal.class, "completionEngine");
+ nameLookupField = accessField(InternalCompletionProposal.class, "nameLookup");
+ createJavaCompletionProposalMethod = accessMethod(CompletionProposalCollector.class, "createJavaCompletionProposal", CompletionProposal.class);
+ }
+
+ private static boolean isComplete() {
+ Object[] requiredFieldsAndMethods = { replacementOffsetField, contextField, extendedContextField, assistNodeField, assistScopeField, lookupEnvironmentField, completionEngineField, nameLookupField, createJavaCompletionProposalMethod };
+ for (Object o : requiredFieldsAndMethods) if (o == null) return false;
+ return true;
+ }
+
+ private static Field accessField(Class<?> clazz, String fieldName) {
+ try {
+ return makeAccessible(clazz.getDeclaredField(fieldName));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static Method accessMethod(Class<?> clazz, String methodName, Class<?> parameter) {
+ try {
+ return makeAccessible(clazz.getDeclaredMethod(methodName, parameter));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static <T extends AccessibleObject> T makeAccessible(T object) {
+ object.setAccessible(true);
+ return object;
+ }
+ }
+}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodPortal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodPortal.java
new file mode 100644
index 00000000..2ccc14ae
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodPortal.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.agent;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+
+import lombok.Lombok;
+
+
+public class PatchExtensionMethodPortal {
+ private static final String TYPE_BINDING = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding";
+ private static final String TYPE_BINDING_ARRAY = "[Lorg.eclipse.jdt.internal.compiler.lookup.TypeBinding;";
+ private static final String MESSAGE_SEND = "org.eclipse.jdt.internal.compiler.ast.MessageSend";
+ private static final String BLOCK_SCOPE = "org.eclipse.jdt.internal.compiler.lookup.BlockScope";
+ private static final String METHOD_BINDING = "org.eclipse.jdt.internal.compiler.lookup.MethodBinding";
+ private static final String PROBLEM_REPORTER = "org.eclipse.jdt.internal.compiler.problem.ProblemReporter";
+ private static final String COMPLETION_PROPOSAL_COLLECTOR = "org.eclipse.jdt.ui.text.java.CompletionProposalCollector";
+ private static final String I_JAVA_COMPLETION_PROPOSAL_ARRAY = "[Lorg.eclipse.jdt.ui.text.java.IJavaCompletionProposal;";
+
+ public static TypeBinding resolveType(Object resolvedType, Object methodCall, Object scope) {
+ try {
+ return (TypeBinding) Reflection.resolveType.invoke(null, resolvedType, methodCall, scope);
+ } catch (NoClassDefFoundError e) {
+ //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
+ //do anything useful here.
+ return (TypeBinding) resolvedType;
+ } catch (IllegalAccessException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (InvocationTargetException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (NullPointerException e) {
+ e.initCause(Reflection.problem);
+ throw e;
+ }
+ }
+
+ public static void errorNoMethodFor(Object problemReporter, Object messageSend, Object recType, Object params) {
+ try {
+ Reflection.errorNoMethodFor.invoke(null, problemReporter, messageSend, recType, params);
+ } catch (NoClassDefFoundError e) {
+ //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
+ //do anything useful here.
+ } catch (IllegalAccessException e) {
+ Lombok.sneakyThrow(e);
+ } catch (InvocationTargetException e) {
+ Lombok.sneakyThrow(e);
+ } catch (NullPointerException e) {
+ e.initCause(Reflection.problem);
+ throw e;
+ }
+ }
+
+ public static void invalidMethod(Object problemReporter, Object messageSend, Object method) {
+ try {
+ Reflection.invalidMethod.invoke(null, problemReporter, messageSend, method);
+ } catch (NoClassDefFoundError e) {
+ //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
+ //do anything useful here.
+ } catch (IllegalAccessException e) {
+ Lombok.sneakyThrow(e);
+ } catch (InvocationTargetException e) {
+ Lombok.sneakyThrow(e);
+ } catch (NullPointerException e) {
+ e.initCause(Reflection.problem);
+ throw e;
+ }
+ }
+
+ public static Object getJavaCompletionProposals(Object[] javaCompletionProposals, Object completionProposalCollector) {
+ try {
+ return Reflection.getJavaCompletionProposals.invoke(null, javaCompletionProposals, completionProposalCollector);
+ } catch (NoClassDefFoundError e) {
+ //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
+ //do anything useful here.
+ return javaCompletionProposals;
+ } catch (IllegalAccessException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (InvocationTargetException e) {
+ throw Lombok.sneakyThrow(e);
+ } catch (NullPointerException e) {
+ e.initCause(Reflection.problem);
+ throw e;
+ }
+ }
+
+ private static final class Reflection {
+ public static final Method resolveType, errorNoMethodFor, invalidMethod, getJavaCompletionProposals;
+ public static final Throwable problem;
+
+ static {
+ Method m = null, n = null, o = null, p = null;
+ Throwable problem_ = null;
+ try {
+ m = PatchExtensionMethod.class.getMethod("resolveType", Class.forName(TYPE_BINDING), Class.forName(MESSAGE_SEND), Class.forName(BLOCK_SCOPE));
+ n = PatchExtensionMethod.class.getMethod("errorNoMethodFor", Class.forName(PROBLEM_REPORTER),
+ Class.forName(MESSAGE_SEND), Class.forName(TYPE_BINDING), Class.forName(TYPE_BINDING_ARRAY));
+ o = PatchExtensionMethod.class.getMethod("invalidMethod", Class.forName(PROBLEM_REPORTER), Class.forName(MESSAGE_SEND), Class.forName(METHOD_BINDING));
+ p = PatchExtensionMethod.class.getMethod("getJavaCompletionProposals", Class.forName(I_JAVA_COMPLETION_PROPOSAL_ARRAY), Class.forName(COMPLETION_PROPOSAL_COLLECTOR));
+ } catch (Throwable t) {
+ // 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.
+ problem_ = t;
+ }
+ resolveType = m;
+ errorNoMethodFor = n;
+ invalidMethod = o;
+ getJavaCompletionProposals = p;
+ problem = problem_;
+ }
+ }
+}