diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-09 18:27:10 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-09 18:27:10 +0200 |
commit | 41fb7cba62b543243c784757d1af4e05824ddc4e (patch) | |
tree | b910a78f31576d08f7a95440edb8f630cf4164eb /src | |
parent | 525211b872b982b880aa2bed5e263ec582593f0d (diff) | |
download | lombok-41fb7cba62b543243c784757d1af4e05824ddc4e.tar.gz lombok-41fb7cba62b543243c784757d1af4e05824ddc4e.tar.bz2 lombok-41fb7cba62b543243c784757d1af4e05824ddc4e.zip |
Many changes:
- Split off the actual agent work into a separate src package in preparation for creating separate jars. Involved a lot of renaming
- Renamed TransformCompilationUnitDeclaration to TransformEclipseAST, as this class will also be transforming e.g. MethodDeclaration objects.
- Expanded the patching to also patch in transform calls when the parser fills in the Statement array for existing constructors, methods, and initializers.
- Redesigned the ClassLoaderWorkaround class quite a bit.
- Positioning should not work correctly ('jump to method' should jump to the getter annotation).
(Apparently, Clinit objects are always fully parsed in the original run, so no need to patch anything there).
Diffstat (limited to 'src')
-rw-r--r-- | src/java/lombok/ClassLoaderWorkaround.java | 74 | ||||
-rw-r--r-- | src/lombok/agent/eclipse/EclipseParserPatcher.java | 47 | ||||
-rw-r--r-- | src/lombok/agent/eclipse/EclipseParserTransformer.java | 83 | ||||
-rw-r--r-- | src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java | 22 | ||||
-rw-r--r-- | src/lombok/eclipse/HandleGetter_ecj.java (renamed from src/lombok/agent/eclipse/HandleGetter_ecj.java) | 9 | ||||
-rw-r--r-- | src/lombok/eclipse/TransformEclipseAST.java | 60 |
6 files changed, 67 insertions, 228 deletions
diff --git a/src/java/lombok/ClassLoaderWorkaround.java b/src/java/lombok/ClassLoaderWorkaround.java deleted file mode 100644 index c533424d..00000000 --- a/src/java/lombok/ClassLoaderWorkaround.java +++ /dev/null @@ -1,74 +0,0 @@ -package java.lombok; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; - -/** - * Allows you to load a class off of any place that is injected into a class loader (which doesn't know how to load the class you're injecting). - * - * Example: Injecting lombok's Eclipse Parser patching code into eclipse's OSGi BundleLoader. - * - * @author rzwitserloot - */ -public class ClassLoaderWorkaround { - private static boolean initialized; - private static Method m; - - public static void transformCompilationUnitDeclaration(Object cud) throws Exception { - if ( !initialized ) initialize(cud); - if ( m == null ) throw new ClassNotFoundException("lombok.agent.eclipse.TransformCompilationUnitDeclaration"); - m.invoke(null, cud); - } - - private static void initialize(Object cud) { - final ClassLoader parent = cud.getClass().getClassLoader(); - ClassLoader loader = new ClassLoader() { - @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - if ( name.startsWith("lombok.") ) { - InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(name.replace(".", "/") + ".class"); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - byte[] b = new byte[65536]; - try { - while ( true ) { - int r = in.read(b); - if ( r == -1 ) break; - if ( r > 0 ) out.write(b, 0, r); - } - - in.close(); - byte[] data = out.toByteArray(); - Class<?> result = defineClass(name, data, 0, data.length); - if ( resolve ) resolveClass(result); - return result; - } catch ( IOException e ) { - throw new ClassNotFoundException(); - } - } else { - try { - Class<?> result = ClassLoader.getSystemClassLoader().loadClass(name); - if ( resolve ) resolveClass(result); - return result; - } catch ( ClassNotFoundException e ) { - Class<?> result = parent.loadClass(name); - if ( resolve ) resolveClass(result); - return result; - } - } - } - }; - - try { - Class<?> c = loader.loadClass("lombok.agent.eclipse.TransformCompilationUnitDeclaration"); - for ( Method m : c.getMethods() ) { - if ( m.getName().equals("transform") ) { - ClassLoaderWorkaround.m = m; - break; - } - } - } catch ( ClassNotFoundException ignore ) {} - initialized = true; - } -} diff --git a/src/lombok/agent/eclipse/EclipseParserPatcher.java b/src/lombok/agent/eclipse/EclipseParserPatcher.java deleted file mode 100644 index abe26683..00000000 --- a/src/lombok/agent/eclipse/EclipseParserPatcher.java +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index fa549ff8..00000000 --- a/src/lombok/agent/eclipse/EclipseParserTransformer.java +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index 7c29a795..00000000 --- a/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java +++ /dev/null @@ -1,22 +0,0 @@ -package lombok.agent.eclipse; - -import org.eclipse.jdt.internal.compiler.ast.Annotation; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; -import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; -import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; - -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") ) { - new HandleGetter_ecj().apply(type, field); - } - } - } - } - } -} diff --git a/src/lombok/agent/eclipse/HandleGetter_ecj.java b/src/lombok/eclipse/HandleGetter_ecj.java index d43bbbc4..f71376a3 100644 --- a/src/lombok/agent/eclipse/HandleGetter_ecj.java +++ b/src/lombok/eclipse/HandleGetter_ecj.java @@ -1,10 +1,12 @@ -package lombok.agent.eclipse; +package lombok.eclipse; import java.lang.reflect.Modifier; import lombok.transformations.TransformationsUtil; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; @@ -16,7 +18,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; public class HandleGetter_ecj { - public void apply(TypeDeclaration type, FieldDeclaration field) { + public void apply(Annotation annotation, TypeDeclaration type, FieldDeclaration field) { TypeReference fieldType = field.type; String getterName = TransformationsUtil.toGetterName(new String(field.name), nameEquals(fieldType.getTypeName(), "boolean")); @@ -34,8 +36,11 @@ public class HandleGetter_ecj { method.thrownExceptions = null; method.typeParameters = null; method.scope = new MethodScope(type.scope, method, false); + method.bits |= ASTNode.Bit24; Expression fieldExpression = new SingleNameReference(field.name, (field.declarationSourceStart << 32) | field.declarationSourceEnd); Statement returnStatement = new ReturnStatement(fieldExpression, field.sourceStart, field.sourceEnd); + method.bodyStart = method.declarationSourceStart = method.sourceStart = annotation.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = annotation.sourceEnd; method.statements = new Statement[] { returnStatement }; AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1]; System.arraycopy(type.methods, 0, newArray, 0, type.methods.length); diff --git a/src/lombok/eclipse/TransformEclipseAST.java b/src/lombok/eclipse/TransformEclipseAST.java new file mode 100644 index 00000000..9212a0d4 --- /dev/null +++ b/src/lombok/eclipse/TransformEclipseAST.java @@ -0,0 +1,60 @@ +package lombok.eclipse; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Initializer; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.parser.Parser; + +/** + * Entry point for the Eclipse Parser patch that lets lombok modify the Abstract Syntax Tree as generated by + * eclipse's parser implementations. This class is injected into the appropriate OSGi ClassLoader and can thus + * use any classes that belong to org.eclipse.jdt.(apt.)core. + * + * Note that, for any Method body, if Bit24 is set, the eclipse parser has been patched to never attempt to + * (re)parse it. You should set Bit24 on any MethodDeclaration object you inject into the AST: + * + * <code>methodDeclaration.bits |= 0x80000;</code> + * + * @author rzwitserloot + * @author rspilker + */ +public class TransformEclipseAST { + /** + * This method is called immediately after eclipse finishes building a CompilationUnitDeclaration, which is + * the top-level AST node when eclipse parses a source file. The signature is 'magic' - you should not + * change it! + * + * Eclipse's parsers often operate in diet mode, which means many parts of the AST have been left blank. + * Be ready to deal with just about anything being null, such as the Statement[] arrays of the Method AST nodes. + * + * @param parser The eclipse parser object that generated the AST. + * @param ast The AST node belonging to the compilation unit (java speak for a single source file). + */ + public static void transform(Parser parser, 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") ) { + new HandleGetter_ecj().apply(annotation, type, field); + } + } + } + } + } + + public static void transform(Parser parser, MethodDeclaration ast) { + + } + + public static void transform(Parser parser, ConstructorDeclaration ast) { + + } + + public static void transform(Parser parser, Initializer ast) { + + } +} |