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_eclipseagent/lombok/agent/eclipse | |
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_eclipseagent/lombok/agent/eclipse')
-rw-r--r-- | src_eclipseagent/lombok/agent/eclipse/EclipseParserPatcher.java | 47 | ||||
-rw-r--r-- | src_eclipseagent/lombok/agent/eclipse/EclipseParserTransformer.java | 157 |
2 files changed, 204 insertions, 0 deletions
diff --git a/src_eclipseagent/lombok/agent/eclipse/EclipseParserPatcher.java b/src_eclipseagent/lombok/agent/eclipse/EclipseParserPatcher.java new file mode 100644 index 00000000..abe26683 --- /dev/null +++ b/src_eclipseagent/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_eclipseagent/lombok/agent/eclipse/EclipseParserTransformer.java b/src_eclipseagent/lombok/agent/eclipse/EclipseParserTransformer.java new file mode 100644 index 00000000..803d5d66 --- /dev/null +++ b/src_eclipseagent/lombok/agent/eclipse/EclipseParserTransformer.java @@ -0,0 +1,157 @@ +package lombok.agent.eclipse; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +class EclipseParserTransformer { + private static final String COMPILER_PKG = + "Lorg/eclipse/jdt/internal/compiler/ast/"; + private static final String TARGET_STATIC_CLASS = "java/lombok/ClassLoaderWorkaround"; + private static final String TARGET_STATIC_METHOD_DESC = "(Ljava/lang/Object;Ljava/lang/Object;)V"; + + private static final Map<String, Class<? extends MethodVisitor>> rewriters; + + static { + Map<String, Class<? extends MethodVisitor>> map = new HashMap<String, Class<? extends MethodVisitor>>(); + map.put(String.format("endParse(I)%sCompilationUnitDeclaration;", COMPILER_PKG), EndParsePatcher.class); + map.put(String.format("parse(%1$sMethodDeclaration;%1$sCompilationUnitDeclaration;)V", COMPILER_PKG), ParseMethodPatcher.class); + map.put(String.format("parse(%1$sConstructorDeclaration;%1$sCompilationUnitDeclaration;Z)V", COMPILER_PKG), ParseConstructorPatcher.class); + map.put(String.format("parse(%1$sInitializer;%1$sTypeDeclaration;%1$sCompilationUnitDeclaration;)V", COMPILER_PKG), ParseInitializerPatcher.class); + rewriters = Collections.unmodifiableMap(map); + } + + 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(); + } + + public static RuntimeException sneakyThrow(Throwable t) { + if ( t == null ) throw new NullPointerException("t"); + EclipseParserTransformer.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } + + 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); + Class<? extends MethodVisitor> targetVisitorClass = rewriters.get(name+desc); + if ( targetVisitorClass == null ) return writerVisitor; + + try { + Constructor<? extends MethodVisitor> c = targetVisitorClass.getDeclaredConstructor(MethodVisitor.class); + c.setAccessible(true); + return c.newInstance(writerVisitor); + } catch ( Exception e ) { + //NoSuchMethodException: We know they exist. + //InvocationTargetException: No checked exceptions thrown by any compilers. + //IllegalAccessException: We called setAccessible. + //InstantiationException: None of these classes are abstract. + throw sneakyThrow(e); + } + } + } + + private static final int BIT24 = 0x800000; + + static class ParseBlockContainerPatcher extends MethodAdapter { + private final String staticMethodName; + + ParseBlockContainerPatcher(MethodVisitor mv, String staticMethodName) { + super(mv); + this.staticMethodName = staticMethodName; + } + + @Override public void visitCode() { + //injects: if ( constructorDeclaration.bits & BIT24 > 0 ) return; + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitFieldInsn(Opcodes.GETFIELD, "org/eclipse/jdt/internal/compiler/ast/ASTNode", "bits", "I"); + mv.visitLdcInsn(Integer.valueOf(BIT24)); + mv.visitInsn(Opcodes.IAND); + Label l0 = new Label(); + mv.visitJumpInsn(Opcodes.IFLE, l0); + mv.visitInsn(Opcodes.RETURN); + mv.visitLabel(l0); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + super.visitCode(); + } + + @Override public void visitInsn(int opcode) { + if ( opcode == Opcodes.RETURN ) { + //injects: ClassLoaderWorkaround.transformConstructorDeclaration(parser, constructorDeclaration); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitVarInsn(Opcodes.ALOAD, 1); + super.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET_STATIC_CLASS, + staticMethodName, TARGET_STATIC_METHOD_DESC); + } + } + } + + static class ParseConstructorPatcher extends ParseBlockContainerPatcher { + public ParseConstructorPatcher(MethodVisitor mv) { + super(mv, "transformConstructorDeclaration"); + } + } + + static class ParseMethodPatcher extends ParseBlockContainerPatcher { + public ParseMethodPatcher(MethodVisitor mv) { + super(mv, "transformMethodDeclaration"); + } + } + + static class ParseInitializerPatcher extends ParseBlockContainerPatcher { + public ParseInitializerPatcher(MethodVisitor mv) { + super(mv, "transformInitializer"); + } + } + + static class EndParsePatcher extends MethodAdapter { + private static final String TARGET_STATIC_METHOD_NAME = "transformCompilationUnitDeclaration"; + + EndParsePatcher(MethodVisitor mv) { + super(mv); + } + + @Override public void visitInsn(int opcode) { + if ( opcode == Opcodes.ARETURN ) { + //injects: ClassLoaderWorkaround.transformCUD(parser, compilationUnitDeclaration); + super.visitInsn(Opcodes.DUP); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.SWAP); + super.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET_STATIC_CLASS, + TARGET_STATIC_METHOD_NAME, TARGET_STATIC_METHOD_DESC); + } + + super.visitInsn(opcode); + } + } +} |