aboutsummaryrefslogtreecommitdiff
path: root/src/lombok/agent/eclipse
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@tipit.to>2009-06-08 22:26:30 +0200
committerReinier Zwitserloot <reinier@tipit.to>2009-06-08 22:26:30 +0200
commit1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce (patch)
tree29de8d12d774f026c0f317e229532befaeb2d096 /src/lombok/agent/eclipse
downloadlombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.tar.gz
lombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.tar.bz2
lombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.zip
Initial commit. As a proof of concept, it already works in javac and eclipse!
Diffstat (limited to 'src/lombok/agent/eclipse')
-rw-r--r--src/lombok/agent/eclipse/EclipseParserPatcher.java47
-rw-r--r--src/lombok/agent/eclipse/EclipseParserTransformer.java83
-rw-r--r--src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java55
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");
+ }
+}