diff options
Diffstat (limited to 'src/lombok/agent/eclipse')
3 files changed, 185 insertions, 0 deletions
diff --git a/src/lombok/agent/eclipse/EclipseParserPatcher.java b/src/lombok/agent/eclipse/EclipseParserPatcher.java new file mode 100644 index 00000000..abe26683 --- /dev/null +++ b/src/lombok/agent/eclipse/EclipseParserPatcher.java @@ -0,0 +1,47 @@ +package lombok.agent.eclipse; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.security.ProtectionDomain; + +public class EclipseParserPatcher { + private static class Patcher implements ClassFileTransformer { + @Override public byte[] transform(ClassLoader loader, String className, + Class<?> classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) + throws IllegalClassFormatException { + + if ( !ECLIPSE_PARSER_CLASS_NAME.equals(className) ) return null; + EclipseParserTransformer transformer = new EclipseParserTransformer(classfileBuffer); + return transformer.transform(); + } + } + + static final String ECLIPSE_PARSER_CLASS_NAME = "org/eclipse/jdt/internal/compiler/parser/Parser"; + + public static void agentmain(String agentArgs, Instrumentation instrumentation) { + registerPatcher(instrumentation, true); + } + + public static void premain(String agentArgs, Instrumentation instrumentation) { + registerPatcher(instrumentation, false); + } + + private static void registerPatcher(Instrumentation instrumentation, boolean transformExisting) { + instrumentation.addTransformer(new Patcher(), true); + + if ( transformExisting ) for ( Class<?> c : instrumentation.getAllLoadedClasses() ) { + if ( c.getName().equals(ECLIPSE_PARSER_CLASS_NAME) ) { + try { + instrumentation.retransformClasses(c); + } catch ( UnmodifiableClassException ex ) { + throw new UnsupportedOperationException( + "The eclipse parser class is already loaded and cannot be modified. " + + "You'll have to restart eclipse in order to use Lombok in eclipse."); + } + } + } + } +} diff --git a/src/lombok/agent/eclipse/EclipseParserTransformer.java b/src/lombok/agent/eclipse/EclipseParserTransformer.java new file mode 100644 index 00000000..fa549ff8 --- /dev/null +++ b/src/lombok/agent/eclipse/EclipseParserTransformer.java @@ -0,0 +1,83 @@ +package lombok.agent.eclipse; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +class EclipseParserTransformer { + private static final String COMPILATION_UNIT_DECLARATION_SIG = + "Lorg/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration;"; + + private static final String TOPATCH_METHOD_NAME = "endParse"; + private static final String TOPATCH_METHOD_DESC = "(I)" + COMPILATION_UNIT_DECLARATION_SIG; + + private final byte[] in; + + EclipseParserTransformer(byte[] classfileBuffer) { + in = classfileBuffer; + } + + byte[] transform() { + ClassReader reader = new ClassReader(in); + ClassWriter writer = new ClassWriter(reader, 0); + ClassAdapter adapter = new ParserPatcherAdapter(writer); + reader.accept(adapter, 0); + return writer.toByteArray(); + } + + private static class ParserPatcherAdapter extends ClassAdapter { + public ParserPatcherAdapter(ClassVisitor cv) { + super(cv); + } + + @Override public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + MethodVisitor writerVisitor = super.visitMethod(access, name, desc, signature, exceptions); + if ( !TOPATCH_METHOD_NAME.equals(name) || !TOPATCH_METHOD_DESC.equals(desc) ) return writerVisitor; + + return new PatcherMethodVisitor(writerVisitor); + } + } + + static class PatcherMethodVisitor extends MethodAdapter { + private static final String TARGET_STATIC_CLASS = "java/lombok/ClassLoaderWorkaround"; + private static final String TARGET_STATIC_METHOD_NAME = "transformCompilationUnitDeclaration"; + private static final String TARGET_STATIC_METHOD_DESC = "(Ljava/lang/Object;)V"; + + private boolean alreadyCalled = false; + + PatcherMethodVisitor(MethodVisitor mv) { + super(mv); + } + + @Override public void visitInsn(int opcode) { + if ( opcode == Opcodes.ARETURN ) insertHookCall(); + + super.visitInsn(opcode); + } + + @Override public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if ( opcode == Opcodes.INVOKESTATIC && + TARGET_STATIC_CLASS.equals(owner) && TARGET_STATIC_METHOD_NAME.equals(name) ) alreadyCalled = true; + super.visitMethodInsn(opcode, owner, name, desc); + } + + /** When this method is called, the stack should hold the reference to the + * just-parsed CompilationUnitDeclaration object that is about to be returned + * to whomever wants it. We will put a call to a method of our choosing in, + * which will transform the CUD. The stack is not modified (that is, that method + * returns a CUD). + */ + private void insertHookCall() { + if ( alreadyCalled ) return; + super.visitInsn(Opcodes.DUP); + super.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET_STATIC_CLASS, + TARGET_STATIC_METHOD_NAME, TARGET_STATIC_METHOD_DESC); + } + } +} diff --git a/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java b/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java new file mode 100644 index 00000000..0bcbc9ae --- /dev/null +++ b/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java @@ -0,0 +1,55 @@ +package lombok.agent.eclipse; + +import java.lang.reflect.Modifier; + +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; + +public class TransformCompilationUnitDeclaration { + /** This is a 'magic' method signature - it is this one that will be called. Don't rename anything! */ + + public static void transform(CompilationUnitDeclaration ast) { + if ( ast.types != null ) for ( TypeDeclaration type : ast.types ) { + if ( type.fields != null ) for ( FieldDeclaration field : type.fields ) { + if ( field.annotations != null ) for ( Annotation annotation : field.annotations ) { + if ( annotation.type.toString().equals("Getter") ) addGetter(type); + } + } + } + } + + private static void addGetter(TypeDeclaration type) { + for ( AbstractMethodDeclaration method : type.methods ) { + if ( method.selector != null && new String(method.selector).equals("getFoo") ) return; + } + + MethodDeclaration method = new MethodDeclaration(type.compilationResult); + method.modifiers = Modifier.PUBLIC; + method.returnType = TypeReference.baseTypeReference(TypeReference.T_int, 0); + method.annotations = null; + method.arguments = null; + method.selector = "getFoo".toCharArray(); + method.binding = null; + method.thrownExceptions = null; + method.typeParameters = null; + method.scope = new MethodScope(type.scope, method, false); + Expression fieldExpression = new SingleNameReference("foo".toCharArray(), 10); + Statement returnStatement = new ReturnStatement(fieldExpression, 1, 2); + method.statements = new Statement[] { returnStatement }; + AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1]; + System.arraycopy(type.methods, 0, newArray, 0, type.methods.length); + newArray[type.methods.length] = method; + type.methods = newArray; + System.out.println("Generated getFoo method"); + } +} |