diff options
Diffstat (limited to 'src/eclipseAgent')
3 files changed, 308 insertions, 0 deletions
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java new file mode 100644 index 00000000..7d2a28bc --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -0,0 +1,215 @@ +/* + * Copyright © 2009 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 + * 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.instrument.Instrumentation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.TargetMatcher; +import lombok.patcher.equinox.EquinoxClassLoader; +import lombok.patcher.scripts.ScriptBuilder; + +/** + * This is a java-agent that patches some of eclipse's classes so AST Nodes are handed off to Lombok + * for modification before Eclipse actually uses them to compile, render errors, show code outlines, + * create auto-completion dialogs, and anything else eclipse does with java code. See the *Transformer + * classes in this package for more information about which classes are transformed and how they are + * transformed. + */ +public class EclipsePatcher { + private EclipsePatcher() {} + + public static void agentmain(@SuppressWarnings("unused") String agentArgs, Instrumentation instrumentation) throws Exception { + registerPatchScripts(instrumentation, true); + } + + public static void premain(@SuppressWarnings("unused") String agentArgs, Instrumentation instrumentation) throws Exception { + registerPatchScripts(instrumentation, false); + } + + private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses) { + ScriptManager sm = new ScriptManager(); + sm.registerTransformer(instrumentation); + EquinoxClassLoader.addPrefix("lombok."); + EquinoxClassLoader.registerScripts(sm); + + patchLombokizeAST(sm); + patchAvoidReparsingGeneratedCode(sm); + patchCatchReparse(sm); + patchSetGeneratedFlag(sm); + patchHideGeneratedNodes(sm); + + if (reloadExistingClasses) sm.reloadClasses(instrumentation); + } + + private static void patchHideGeneratedNodes(ScriptManager sm) { + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByNode")) + .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByBinding")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "removeGeneratedSimpleNames", + "([Lorg/eclipse/jdt/core/dom/SimpleName;)[Lorg/eclipse/jdt/core/dom/SimpleName;")) + .request(StackRequest.RETURN_VALUE).build()); + + patchRefactorScripts(sm); + patchFormatters(sm); + } + + private static void patchFormatters(ScriptManager sm) { + sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.ui.text.java.JavaFormattingStrategy", "format", "void")) + .callToWrap(new Hook("org/eclipse/jdt/internal/corext/util/CodeFormatterUtil", "reformat", + "(ILjava/lang/String;IIILjava/lang/String;Ljava/util/Map;)Lorg/eclipse/text/edits/TextEdit;")) + .symbol("lombok.disable").build()); + } + + private static void patchRefactorScripts(ScriptManager sm) { + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "replace")) + .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "remove")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "skipRewritingGeneratedNodes", + "(Lorg/eclipse/jdt/core/dom/ASTNode;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor", "addConstructorRenames")) + .methodToWrap(new Hook("org/eclipse/jdt/core/IType", "getMethods", "()[Lorg/eclipse/jdt/core/IMethod;")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "removeGeneratedMethods", + "([Lorg/eclipse/jdt/core/IMethod;)[Lorg/eclipse/jdt/core/IMethod;")) + .transplant().build()); + } + + private static void patchCatchReparse(ScriptManager sm) { + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveStartingCatchPosition")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "fixRetrieveStartingCatchPosition", "(I)I")) + .transplant().request(StackRequest.PARAM1).build()); + } + + private static void patchSetGeneratedFlag(ScriptManager sm) { + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.internal.compiler.ast.ASTNode") + .fieldName("$generatedBy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic().setTransient().build()); + + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.core.dom.ASTNode") + .fieldName("$isGenerated").fieldType("Z") + .setPublic().setTransient().build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new TargetMatcher() { + @Override public boolean matches(String classSpec, String methodName, String descriptor) { + if (!"convert".equals(methodName)) return false; + + List<String> fullDesc = MethodTarget.decomposeFullDesc(descriptor); + if ("V".equals(fullDesc.get(0))) return false; + if (fullDesc.size() < 2) return false; + if (!fullDesc.get(1).startsWith("Lorg/eclipse/jdt/internal/compiler/ast/")) return false; + return true; + } + + @Override public Collection<String> getAffectedClasses() { + return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); + } + }).request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "setIsGeneratedFlag", + "(Lorg/eclipse/jdt/core/dom/ASTNode;Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;)V")) + .transplant().build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new TargetMatcher() { + @Override public boolean matches(String classSpec, String methodName, String descriptor) { + if (!methodName.startsWith("convert")) return false; + + List<String> fullDesc = MethodTarget.decomposeFullDesc(descriptor); + if (fullDesc.size() < 2) return false; + if (!fullDesc.get(1).startsWith("Lorg/eclipse/jdt/internal/compiler/ast/")) return false; + + return true; + } + + @Override public Collection<String> getAffectedClasses() { + return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); + } + }).methodToWrap(new Hook("org/eclipse/jdt/core/dom/SimpleName", "<init>", "(Lorg/eclipse/jdt/core/dom/AST;)V")) + .requestExtra(StackRequest.PARAM1) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "setIsGeneratedFlagForSimpleName", + "(Lorg/eclipse/jdt/core/dom/SimpleName;Ljava/lang/Object;)V")) + .transplant().build()); + } + + private static void patchAvoidReparsingGeneratedCode(ScriptManager sm) { + final String PARSER_SIG1 = "org.eclipse.jdt.internal.compiler.parser.Parser"; + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.MethodDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "boolean")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.Initializer", + "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + } + + private static void patchLombokizeAST(ScriptManager sm) { + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration") + .fieldName("$lombokAST").fieldType("Ljava/lang/Object;") + .setPublic().setTransient().build()); + + final String PARSER_SIG1 = "org.eclipse.jdt.internal.compiler.parser.Parser"; + final String PARSER_SIG2 = "Lorg/eclipse/jdt/internal/compiler/parser/Parser;"; + final String CUD_SIG1 = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + final String CUD_SIG2 = "Lorg/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration;"; + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG1, "getMethodBodies", "void", CUD_SIG1)) + .wrapMethod(new Hook("lombok/eclipse/TransformEclipseAST", "transform", + "(" + PARSER_SIG2 + CUD_SIG2 + ")V")) + .request(StackRequest.THIS, StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG1, "endParse", CUD_SIG1, "int")) + .wrapMethod(new Hook("lombok/eclipse/TransformEclipseAST", "transform_swapped", + "(" + CUD_SIG2 + PARSER_SIG2 + ")V")) + .request(StackRequest.THIS, StackRequest.RETURN_VALUE).build()); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java new file mode 100644 index 00000000..5d54692e --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -0,0 +1,67 @@ +package lombok.eclipse.agent; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.SimpleName; + +public class PatchFixes { + public static int fixRetrieveStartingCatchPosition(int in) { + return in; + } + + private static final int BIT24 = 0x800000; + + public static boolean checkBit24(Object node) throws Exception { + int bits = (Integer)(node.getClass().getField("bits").get(node)); + return (bits & BIT24) != 0; + } + + public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { + return ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); + } + + public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, + org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { + boolean isGenerated = internalNode.getClass().getField("$generatedBy").get(internalNode) != null; + if (isGenerated) { + domNode.getClass().getField("$isGenerated").set(domNode, true); + domNode.setFlags(domNode.getFlags() & ~ASTNode.ORIGINAL); + } + } + + public static void setIsGeneratedFlagForSimpleName(SimpleName name, Object internalNode) throws Exception { + if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { + if (internalNode.getClass().getField("$generatedBy").get(internalNode) != null) { + name.getClass().getField("$isGenerated").set(name, true); + } + } + } + + public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { + List<IMethod> result = new ArrayList<IMethod>(); + for (IMethod m : methods) { + if (m.getNameRange().getLength() > 0) result.add(m); + } + return result.size() == methods.length ? methods : result.toArray(new IMethod[0]); + } + + public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { + Field f = SimpleName.class.getField("$isGenerated"); + + int count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; + } + if (count == in.length) return in; + SimpleName[] newSimpleNames = new SimpleName[count]; + count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; + } + return newSimpleNames; + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/package-info.java b/src/eclipseAgent/lombok/eclipse/agent/package-info.java new file mode 100644 index 00000000..12255f81 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2009 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 + * 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. + */ + +/** + * Contains the mechanism that instruments eclipse by being loaded as a javaagent. + */ +package lombok.eclipse.agent; |