diff options
Diffstat (limited to 'src/eclipseAgent/lombok/eclipse/agent/PatchVal.java')
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/PatchVal.java | 328 |
1 files changed, 19 insertions, 309 deletions
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; |