Port of lombok-pg's @ExtensionMethod support for eclipse. Doesn't work yet.

This commit is contained in:
Reinier Zwitserloot committed 2012-06-12 18:53:47 +02:00
1 parent a6a8e4c555
commit 7496a8f170
7 files changed
+876 -1

No files matched your search

@@ -0,0 +1,57 @@
/*
* 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.handlers;
import java.util.List;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import lombok.core.AnnotationValues;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.experimental.ExtensionMethod;
// This handler just does some additional error checking; the real work is done in the agent.
public class HandleExtensionMethod extends EclipseAnnotationHandler<ExtensionMethod> {
@Override public void handle(AnnotationValues<ExtensionMethod> annotation, Annotation ast, EclipseNode annotationNode) {
TypeDeclaration typeDecl = null;
EclipseNode owner = annotationNode.up();
if (owner.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) owner.get();
int modifiers = typeDecl == null ? 0 : typeDecl.modifiers;
boolean notAClass = (modifiers &
(ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0;
if (typeDecl == null || notAClass) {
annotationNode.addError("@ExtensionMethod is legal only on classes and enums.");
return;
}
List<Object> listenerInterfaces = annotation.getActualExpressions("value");
if (listenerInterfaces.isEmpty()) {
annotationNode.addWarning(String.format("@ExtensionMethod has no effect since no extension types were specified."));
return;
}
}
}
@@ -60,5 +60,9 @@ public @interface ExtensionMethod {
/** All types whose static methods will be exposed as extension methods. */
Class<?>[] value();
/**
* If {@code true}, an applicable extension method is used (if found) even if the method call already was compilable (this is the default).
* If {@code false}, an extension method is only used if the method call is not also defined by the type itself.
*/
boolean suppressBaseMethods() default true;
}
@@ -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());
}
}
}
@@ -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;
}
}
}
@@ -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_;
}
}
}
@@ -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;
}
}
}
@@ -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_;
}
}
}