diff options
5 files changed, 395 insertions, 330 deletions
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index ea53835a..f28ba5a1 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -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) { diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java index ea54be48..5680e8e1 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java @@ -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; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index b624caff..4c2b088d 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -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; } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java index 68b90286..b19e0742 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java @@ -1,5 +1,5 @@ /* - * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * Copyright © 2010-2011 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -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; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java b/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java new file mode 100644 index 00000000..3fb407b9 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java @@ -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; + } + } +} |