Split up PatchVal into the ecj and eclipse bits; in ecj you'd just get NoSuchClassErrors.

This commit is contained in:
Reinier Zwitserloot committed 2011-05-16 22:32:27 +02:00
1 parent 8b2ee73cc4
commit 75e81604f3
5 files changed
+395 -330

No files matched your search

@@ -1,5 +1,5 @@
/*
* Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker.
* Copyright © 2009-2011 Reinier Zwitserloot and Roel Spilker.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -43,9 +43,31 @@ import lombok.patcher.scripts.ScriptBuilder;
* transformed.
*/
public class EclipsePatcher extends Agent {
// At some point I'd like the agent to be capable of auto-detecting if its on eclipse or on ecj. This class is a sure sign we're not in ecj but in eclipse. -ReinierZ
@SuppressWarnings("unused")
private static final String ECLIPSE_SIGNATURE_CLASS = "org/eclipse/core/runtime/adaptor/EclipseStarter";
@Override
public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception {
registerPatchScripts(instrumentation, injected, injected);
String[] args = agentArgs == null ? new String[0] : agentArgs.split(":");
boolean forceEcj = false;
boolean forceEclipse = false;
for (String arg : args) {
if (arg.trim().equalsIgnoreCase("ECJ")) forceEcj = true;
if (arg.trim().equalsIgnoreCase("ECLIPSE")) forceEclipse = true;
}
if (forceEcj && forceEclipse) {
forceEcj = false;
forceEclipse = false;
}
boolean ecj;
if (forceEcj) ecj = true;
else if (forceEclipse) ecj = false;
else ecj = injected;
registerPatchScripts(instrumentation, injected, ecj);
}
private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) {
@@ -274,8 +296,93 @@ public class EclipsePatcher extends Agent {
}
private static void patchEcjTransformers(ScriptManager sm, boolean ecj) {
PatchDelegate.addPatches(sm, ecj);
PatchVal.addPatches(sm, ecj);
addPatchesForDelegate(sm, ecj);
addPatchesForVal(sm);
if (!ecj) addPatchesForValEclipse(sm);
}
private static void addPatchesForDelegate(ScriptManager sm, boolean ecj) {
final String CLASSSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.ClassScope";
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void"))
.request(StackRequest.THIS)
.decisionMethod(new Hook(PatchDelegate.class.getName(), "handleDelegateForType", "boolean", CLASSSCOPE_SIG))
.build());
}
private static void addPatchesForValEclipse(ScriptManager sm) {
final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration";
final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser";
final String VARIABLEDECLARATIONSTATEMENT_SIG = "org.eclipse.jdt.core.dom.VariableDeclarationStatement";
final String ASTCONVERTER_SIG = "org.eclipse.jdt.core.dom.ASTConverter";
sm.addScript(ScriptBuilder.addField()
.fieldName("$initCopy")
.fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;")
.setPublic()
.setTransient()
.targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")
.build());
sm.addScript(ScriptBuilder.addField()
.fieldName("$iterableCopy")
.fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;")
.setPublic()
.setTransient()
.targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void"))
.request(StackRequest.THIS)
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfLocalDeclaration", "void", PARSER_SIG))
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void"))
.request(StackRequest.THIS)
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfForEachIterable", "void", PARSER_SIG))
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG))
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "addFinalAndValAnnotationToVariableDeclarationStatement",
"void", "java.lang.Object", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG))
.transplant().request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build());
}
private static void addPatchesForVal(ScriptManager sm) {
final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration";
final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement";
final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression";
final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope";
final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding";
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.request(StackRequest.THIS, StackRequest.PARAM1)
.decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG))
.build());
sm.addScript(ScriptBuilder.replaceMethodCall()
.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG))
.requestExtra(StackRequest.THIS)
.replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG))
.build());
sm.addScript(ScriptBuilder.replaceMethodCall()
.target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG))
.replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG))
.build());
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.request(StackRequest.THIS, StackRequest.PARAM1)
.decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG))
.build());
}
private static void patchFixSourceTypeConverter(ScriptManager sm) {
@@ -37,11 +37,6 @@ import lombok.eclipse.EclipseAST;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.TransformEclipseAST;
import lombok.eclipse.handlers.EclipseHandlerUtil;
import lombok.patcher.Hook;
import lombok.patcher.MethodTarget;
import lombok.patcher.ScriptManager;
import lombok.patcher.StackRequest;
import lombok.patcher.scripts.ScriptBuilder;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
@@ -84,16 +79,6 @@ import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
public class PatchDelegate {
static void addPatches(ScriptManager sm, boolean ecj) {
final String CLASSSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.ClassScope";
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void"))
.request(StackRequest.THIS)
.decisionMethod(new Hook(PatchDelegate.class.getName(), "handleDelegateForType", "boolean", CLASSSCOPE_SIG))
.build());
}
private static class ClassScopeEntry {
ClassScopeEntry(ClassScope scope) {
this.scope = scope;
@@ -35,7 +35,6 @@ import lombok.core.PostCompiler;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
@@ -144,7 +143,7 @@ public class PatchFixes {
try {
in = annotatable.getAnnotations();
} catch (JavaModelException e) {
} catch (Exception e) {
return out;
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans.
* Copyright © 2010-2011 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -18,34 +18,13 @@
* 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.
*
* Thanks to Stephen Haberman for a patch to solve some NPEs in Eclipse.
*/
package lombok.eclipse.agent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import lombok.Lombok;
import lombok.eclipse.Eclipse;
import lombok.patcher.Hook;
import lombok.patcher.MethodTarget;
import lombok.patcher.ScriptManager;
import lombok.patcher.StackRequest;
import lombok.patcher.scripts.ScriptBuilder;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
@@ -62,275 +41,15 @@ import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.parser.Parser;
public class PatchVal {
// This is half of the work for 'val' support - the other half is in PatchValEclipse. This half is enough for ecj.
// 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),
// and patches .resolve() on LocalDeclaration itself to just-in-time replace the 'val' vartype with the right one.
static void addPatches(ScriptManager sm, boolean ecj) {
final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration";
final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement";
final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression";
final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope";
final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser";
final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding";
final String VARIABLEDECLARATIONSTATEMENT_SIG = "org.eclipse.jdt.core.dom.VariableDeclarationStatement";
final String ASTCONVERTER_SIG = "org.eclipse.jdt.core.dom.ASTConverter";
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.request(StackRequest.THIS, StackRequest.PARAM1)
.decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG))
.build());
sm.addScript(ScriptBuilder.replaceMethodCall()
.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG))
.requestExtra(StackRequest.THIS)
.replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG))
.build());
sm.addScript(ScriptBuilder.replaceMethodCall()
.target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG))
.replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG))
.build());
sm.addScript(ScriptBuilder.exitEarly()
.target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG))
.request(StackRequest.THIS, StackRequest.PARAM1)
.decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG))
.build());
if (!ecj) {
sm.addScript(ScriptBuilder.addField()
.fieldName("$initCopy")
.fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;")
.setPublic()
.setTransient()
.targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")
.build());
sm.addScript(ScriptBuilder.addField()
.fieldName("$iterableCopy")
.fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;")
.setPublic()
.setTransient()
.targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void"))
.request(StackRequest.THIS)
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfLocalDeclaration", "void", PARSER_SIG))
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void"))
.request(StackRequest.THIS)
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "copyInitializationOfForEachIterable", "void", PARSER_SIG))
.build());
sm.addScript(ScriptBuilder.wrapReturnValue()
.target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG))
.wrapMethod(new Hook("lombok.eclipse.agent.PatchVal", "addFinalAndValAnnotationToVariableDeclarationStatement",
"void", "java.lang.Object", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG))
.transplant().request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build());
}
}
public static final class Reflection {
public static final Field initCopyField, iterableCopyField;
public static final Field astStackField, astPtrField;
public static final Constructor<Modifier> modifierConstructor;
public static final Constructor<MarkerAnnotation> markerAnnotationConstructor;
public static final Method astConverterRecordNodes;
static {
Field a = null, b = null, c = null, d = null;
Constructor<Modifier> f = null;
Constructor<MarkerAnnotation> g = null;
Method h = null;
try {
a = LocalDeclaration.class.getDeclaredField("$initCopy");
b = LocalDeclaration.class.getDeclaredField("$iterableCopy");
} catch (Throwable t) {
//ignore - no $initCopy exists when running in ecj.
}
try {
c = Parser.class.getDeclaredField("astStack");
c.setAccessible(true);
d = Parser.class.getDeclaredField("astPtr");
d.setAccessible(true);
f = Modifier.class.getDeclaredConstructor(AST.class);
f.setAccessible(true);
g = MarkerAnnotation.class.getDeclaredConstructor(AST.class);
g.setAccessible(true);
Class<?> z = Class.forName("org.eclipse.jdt.core.dom.ASTConverter");
h = z.getDeclaredMethod("recordNodes", org.eclipse.jdt.core.dom.ASTNode.class, org.eclipse.jdt.internal.compiler.ast.ASTNode.class);
h.setAccessible(true);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
}
initCopyField = a;
iterableCopyField = b;
astStackField = c;
astPtrField = d;
modifierConstructor = f;
markerAnnotationConstructor = g;
astConverterRecordNodes = h;
}
}
public static void copyInitializationOfLocalDeclaration(Parser parser) {
ASTNode[] astStack;
int astPtr;
try {
astStack = (ASTNode[]) Reflection.astStackField.get(parser);
astPtr = (Integer)Reflection.astPtrField.get(parser);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
return;
}
AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr];
if (!(variableDecl instanceof LocalDeclaration)) return;
ASTNode init = variableDecl.initialization;
if (init == null) return;
if (variableDecl.type instanceof SingleTypeReference) {
SingleTypeReference ref = (SingleTypeReference) variableDecl.type;
if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return;
} else return;
try {
if (Reflection.initCopyField != null) Reflection.initCopyField.set(variableDecl, init);
} catch (Exception e) {
// In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception.
}
}
public static void addFinalAndValAnnotationToVariableDeclarationStatement(Object converter, VariableDeclarationStatement out, LocalDeclaration in) {
// First check that 'in' has the final flag on, and a @val / @lombok.val annotation.
if ((in.modifiers & ClassFileConstants.AccFinal) == 0) return;
if (in.annotations == null) return;
boolean found = false;
Annotation valAnnotation = null;
for (Annotation ann : in.annotations) {
if (ann.type instanceof SingleTypeReference) {
if (matches("val", ((SingleTypeReference)ann.type).token)) {
found = true;
valAnnotation = ann;
break;
}
}
if (ann.type instanceof QualifiedTypeReference) {
char[][] tokens = ((QualifiedTypeReference)ann.type).tokens;
if (tokens != null && tokens.length == 2 && matches("lombok", tokens[0]) && matches("val", tokens[1])) {
found = true;
valAnnotation = ann;
break;
}
}
}
if (!found) return;
// Now check that 'out' is missing either of these.
@SuppressWarnings("unchecked") List<IExtendedModifier> modifiers = out.modifiers();
if (modifiers == null) return; // This is null only if the project is 1.4 or less. Lombok doesn't work in that.
boolean finalIsPresent = false;
boolean valIsPresent = false;
for (Object present : modifiers) {
if (present instanceof Modifier) {
ModifierKeyword keyword = ((Modifier)present).getKeyword();
if (keyword == null) continue;
if (keyword.toFlagValue() == Modifier.FINAL) finalIsPresent = true;
}
if (present instanceof org.eclipse.jdt.core.dom.Annotation) {
Name typeName = ((org.eclipse.jdt.core.dom.Annotation) present).getTypeName();
if (typeName != null) {
String fullyQualifiedName = typeName.getFullyQualifiedName();
if ("val".equals(fullyQualifiedName) || "lombok.val".equals(fullyQualifiedName)) {
valIsPresent = true;
}
}
}
}
if (!finalIsPresent) {
modifiers.add(
createModifier(out.getAST(), ModifierKeyword.FINAL_KEYWORD, valAnnotation.sourceStart, valAnnotation.sourceEnd));
}
if (!valIsPresent) {
MarkerAnnotation newAnnotation = createValAnnotation(out.getAST(), valAnnotation, valAnnotation.sourceStart, valAnnotation.sourceEnd);
try {
Reflection.astConverterRecordNodes.invoke(converter, newAnnotation, valAnnotation);
Reflection.astConverterRecordNodes.invoke(converter, newAnnotation.getTypeName(), valAnnotation.type);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e.getCause());
}
modifiers.add(newAnnotation);
}
}
public static MarkerAnnotation createValAnnotation(AST ast, Annotation original, int start, int end) {
MarkerAnnotation out = null;
try {
out = Reflection.markerAnnotationConstructor.newInstance(ast);
} catch (InstantiationException e) {
Lombok.sneakyThrow(e);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e);
}
if (out != null) {
if (original.type instanceof SingleTypeReference) {
out.setTypeName(ast.newSimpleName("val"));
} else {
out.setTypeName(ast.newQualifiedName(ast.newSimpleName("lombok"), ast.newSimpleName("val")));
}
out.setSourceRange(start, end - start + 1);
}
return out;
}
public static Modifier createModifier(AST ast, ModifierKeyword keyword, int start, int end) {
Modifier modifier = null;
try {
modifier = Reflection.modifierConstructor.newInstance(ast);
} catch (InstantiationException e) {
Lombok.sneakyThrow(e);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e);
}
if (modifier != null) {
modifier.setKeyword(keyword);
modifier.setSourceRange(start, end - start + 1);
}
return modifier;
}
public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) {
if (expr.resolvedType != null) return expr.resolvedType;
return expr.resolveType(scope);
@@ -377,6 +96,23 @@ public class PatchVal {
return matches("lombok", pkg) && matches("val", nm);
}
public static final class Reflection {
private static final Field initCopyField, iterableCopyField;
static {
Field a = null, b = null;
try {
a = LocalDeclaration.class.getDeclaredField("$initCopy");
b = LocalDeclaration.class.getDeclaredField("$iterableCopy");
} catch (Throwable t) {
//ignore - no $initCopy exists when running in ecj.
}
initCopyField = a;
iterableCopyField = b;
}
}
public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) {
if (local == null || !LocalDeclaration.class.equals(local.getClass())) return false;
boolean decomponent = false;
@@ -417,32 +153,6 @@ public class PatchVal {
return false;
}
public static void copyInitializationOfForEachIterable(Parser parser) {
ASTNode[] astStack;
int astPtr;
try {
astStack = (ASTNode[]) Reflection.astStackField.get(parser);
astPtr = (Integer) Reflection.astPtrField.get(parser);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
return;
}
ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr];
ASTNode init = foreachDecl.collection;
if (init == null) return;
if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) {
SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.type;
if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return;
} else return;
try {
if (Reflection.iterableCopyField != null) Reflection.iterableCopyField.set(foreachDecl.elementVariable, init);
} catch (Exception e) {
// In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception.
}
}
public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) {
if (forEach.elementVariable == null) return false;
@@ -0,0 +1,264 @@
/*
* Copyright © 2010-2011 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* 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.
*
* Thanks to Stephen Haberman for a patch to solve some NPEs in Eclipse.
*/
package lombok.eclipse.agent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import lombok.Lombok;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.parser.Parser;
public class PatchValEclipse {
public static void copyInitializationOfForEachIterable(Parser parser) {
ASTNode[] astStack;
int astPtr;
try {
astStack = (ASTNode[]) Reflection.astStackField.get(parser);
astPtr = (Integer) Reflection.astPtrField.get(parser);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
return;
}
ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr];
ASTNode init = foreachDecl.collection;
if (init == null) return;
if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) {
SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.type;
if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return;
} else return;
try {
if (Reflection.iterableCopyField != null) Reflection.iterableCopyField.set(foreachDecl.elementVariable, init);
} catch (Exception e) {
// In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception.
}
}
public static void copyInitializationOfLocalDeclaration(Parser parser) {
ASTNode[] astStack;
int astPtr;
try {
astStack = (ASTNode[]) Reflection.astStackField.get(parser);
astPtr = (Integer)Reflection.astPtrField.get(parser);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
return;
}
AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr];
if (!(variableDecl instanceof LocalDeclaration)) return;
ASTNode init = variableDecl.initialization;
if (init == null) return;
if (variableDecl.type instanceof SingleTypeReference) {
SingleTypeReference ref = (SingleTypeReference) variableDecl.type;
if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return;
} else return;
try {
if (Reflection.initCopyField != null) Reflection.initCopyField.set(variableDecl, init);
} catch (Exception e) {
// In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception.
}
}
public static void addFinalAndValAnnotationToVariableDeclarationStatement(Object converter, VariableDeclarationStatement out, LocalDeclaration in) {
// First check that 'in' has the final flag on, and a @val / @lombok.val annotation.
if ((in.modifiers & ClassFileConstants.AccFinal) == 0) return;
if (in.annotations == null) return;
boolean found = false;
Annotation valAnnotation = null;
for (Annotation ann : in.annotations) {
if (ann.type instanceof SingleTypeReference) {
if (PatchVal.matches("val", ((SingleTypeReference)ann.type).token)) {
found = true;
valAnnotation = ann;
break;
}
}
if (ann.type instanceof QualifiedTypeReference) {
char[][] tokens = ((QualifiedTypeReference)ann.type).tokens;
if (tokens != null && tokens.length == 2 && PatchVal.matches("lombok", tokens[0]) && PatchVal.matches("val", tokens[1])) {
found = true;
valAnnotation = ann;
break;
}
}
}
if (!found) return;
// Now check that 'out' is missing either of these.
@SuppressWarnings("unchecked") List<IExtendedModifier> modifiers = out.modifiers();
if (modifiers == null) return; // This is null only if the project is 1.4 or less. Lombok doesn't work in that.
boolean finalIsPresent = false;
boolean valIsPresent = false;
for (Object present : modifiers) {
if (present instanceof Modifier) {
ModifierKeyword keyword = ((Modifier)present).getKeyword();
if (keyword == null) continue;
if (keyword.toFlagValue() == Modifier.FINAL) finalIsPresent = true;
}
if (present instanceof org.eclipse.jdt.core.dom.Annotation) {
Name typeName = ((org.eclipse.jdt.core.dom.Annotation) present).getTypeName();
if (typeName != null) {
String fullyQualifiedName = typeName.getFullyQualifiedName();
if ("val".equals(fullyQualifiedName) || "lombok.val".equals(fullyQualifiedName)) {
valIsPresent = true;
}
}
}
}
if (!finalIsPresent) {
modifiers.add(
createModifier(out.getAST(), ModifierKeyword.FINAL_KEYWORD, valAnnotation.sourceStart, valAnnotation.sourceEnd));
}
if (!valIsPresent) {
MarkerAnnotation newAnnotation = createValAnnotation(out.getAST(), valAnnotation, valAnnotation.sourceStart, valAnnotation.sourceEnd);
try {
Reflection.astConverterRecordNodes.invoke(converter, newAnnotation, valAnnotation);
Reflection.astConverterRecordNodes.invoke(converter, newAnnotation.getTypeName(), valAnnotation.type);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e.getCause());
}
modifiers.add(newAnnotation);
}
}
public static Modifier createModifier(AST ast, ModifierKeyword keyword, int start, int end) {
Modifier modifier = null;
try {
modifier = Reflection.modifierConstructor.newInstance(ast);
} catch (InstantiationException e) {
Lombok.sneakyThrow(e);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e);
}
if (modifier != null) {
modifier.setKeyword(keyword);
modifier.setSourceRange(start, end - start + 1);
}
return modifier;
}
public static MarkerAnnotation createValAnnotation(AST ast, Annotation original, int start, int end) {
MarkerAnnotation out = null;
try {
out = Reflection.markerAnnotationConstructor.newInstance(ast);
} catch (InstantiationException e) {
Lombok.sneakyThrow(e);
} catch (IllegalAccessException e) {
Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
Lombok.sneakyThrow(e);
}
if (out != null) {
if (original.type instanceof SingleTypeReference) {
out.setTypeName(ast.newSimpleName("val"));
} else {
out.setTypeName(ast.newQualifiedName(ast.newSimpleName("lombok"), ast.newSimpleName("val")));
}
out.setSourceRange(start, end - start + 1);
}
return out;
}
public static final class Reflection {
private static final Field initCopyField, iterableCopyField;
private static final Field astStackField, astPtrField;
private static final Constructor<Modifier> modifierConstructor;
private static final Constructor<MarkerAnnotation> markerAnnotationConstructor;
private static final Method astConverterRecordNodes;
static {
Field a = null, b = null, c = null, d = null;
Constructor<Modifier> f = null;
Constructor<MarkerAnnotation> g = null;
Method h = null;
try {
a = LocalDeclaration.class.getDeclaredField("$initCopy");
b = LocalDeclaration.class.getDeclaredField("$iterableCopy");
} catch (Throwable t) {
//ignore - no $initCopy exists when running in ecj.
}
try {
c = Parser.class.getDeclaredField("astStack");
c.setAccessible(true);
d = Parser.class.getDeclaredField("astPtr");
d.setAccessible(true);
f = Modifier.class.getDeclaredConstructor(AST.class);
f.setAccessible(true);
g = MarkerAnnotation.class.getDeclaredConstructor(AST.class);
g.setAccessible(true);
Class<?> z = Class.forName("org.eclipse.jdt.core.dom.ASTConverter");
h = z.getDeclaredMethod("recordNodes", org.eclipse.jdt.core.dom.ASTNode.class, org.eclipse.jdt.internal.compiler.ast.ASTNode.class);
h.setAccessible(true);
} catch (Exception e) {
// Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this.
}
initCopyField = a;
iterableCopyField = b;
astStackField = c;
astPtrField = d;
modifierConstructor = f;
markerAnnotationConstructor = g;
astConverterRecordNodes = h;
}
}
}