diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-08 22:26:30 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-08 22:26:30 +0200 |
commit | 1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce (patch) | |
tree | 29de8d12d774f026c0f317e229532befaeb2d096 /src | |
download | lombok-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')
-rw-r--r-- | src/java/lombok/ClassLoaderWorkaround.java | 74 | ||||
-rw-r--r-- | src/lombok/AccessLevel.java | 5 | ||||
-rw-r--r-- | src/lombok/Getter.java | 12 | ||||
-rw-r--r-- | src/lombok/Lombok.java | 31 | ||||
-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 | 55 | ||||
-rw-r--r-- | src/lombok/apt/AnnotationTransponder.java | 111 | ||||
-rw-r--r-- | src/lombok/apt/HandleANY_ecj.java | 18 | ||||
-rw-r--r-- | src/lombok/apt/HandleGetter_javac.java | 75 | ||||
-rw-r--r-- | src/lombok/apt/HandlerForCompiler.java | 16 | ||||
-rw-r--r-- | src/lombok/apt/PKG.java | 77 | ||||
-rw-r--r-- | src/lombok/apt/Processor.java | 36 |
13 files changed, 640 insertions, 0 deletions
diff --git a/src/java/lombok/ClassLoaderWorkaround.java b/src/java/lombok/ClassLoaderWorkaround.java new file mode 100644 index 00000000..c533424d --- /dev/null +++ b/src/java/lombok/ClassLoaderWorkaround.java @@ -0,0 +1,74 @@ +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/AccessLevel.java b/src/lombok/AccessLevel.java new file mode 100644 index 00000000..37b91235 --- /dev/null +++ b/src/lombok/AccessLevel.java @@ -0,0 +1,5 @@ +package lombok; + +public enum AccessLevel { + PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE; +} diff --git a/src/lombok/Getter.java b/src/lombok/Getter.java new file mode 100644 index 00000000..5636c169 --- /dev/null +++ b/src/lombok/Getter.java @@ -0,0 +1,12 @@ +package lombok; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface Getter { + AccessLevel value() default AccessLevel.PUBLIC; +} diff --git a/src/lombok/Lombok.java b/src/lombok/Lombok.java new file mode 100644 index 00000000..10a58e5a --- /dev/null +++ b/src/lombok/Lombok.java @@ -0,0 +1,31 @@ +package lombok; + +public class Lombok { + /** + * Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it onwards. + * The exception is still thrown - javac will just stop whining about it. + * + * Example usage: + * + * <pre>public void run() { + * throw sneakyThrow(new IOException("You don't need to catch me!")); + * }</pre> + * + * NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care + * about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception + * from the java compiler. + * + * @param t The throwable to throw without requiring you to catch its type. + * @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws an exception! + */ + public static RuntimeException sneakyThrow(Throwable t) { + if ( t == null ) throw new NullPointerException("t"); + Lombok.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } +} 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"); + } +} diff --git a/src/lombok/apt/AnnotationTransponder.java b/src/lombok/apt/AnnotationTransponder.java new file mode 100644 index 00000000..01a79c12 --- /dev/null +++ b/src/lombok/apt/AnnotationTransponder.java @@ -0,0 +1,111 @@ +package lombok.apt; + +import static lombok.apt.PKG.CURRENT_SUPPORT; +import static lombok.apt.PKG.isInstanceOf; +import static lombok.apt.PKG.readResource; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; + +/** + * Responsible for redirecting the need to handle an annotation to a class that knows how to handle a given annotation type in a given compiler environment. + * Will dynamically locate a class in this package using the naming pattern: "HandleFoo_compilerType", e.g. "HandleGetter_ecj". + * Responsible for injecting the proper class into the right classloader so that it has open access to the classes required to inspect the live AST and + * modify it so that annotations can cause changes to the live AST. + * + * @author rzwitserloot + * + * @param <T> The annotation class that this transponder should handle (example: Getter.class). + */ +public class AnnotationTransponder<T extends Annotation> { + private HandlerForCompiler<T> impl; + private final ProcessingEnvironment processEnv; + private final RoundEnvironment roundEnv; + private String error; + + @SuppressWarnings("unchecked") + private void createInstance(Class<T> annotation, ClassLoader loader, String compilerType) { + try { + if ( loader == null ) loader = AnnotationTransponder.class.getClassLoader(); + Class<?> implClass; + try { + implClass = loader.loadClass(String.format( + "org.javanext.apt.Handle%s_%s", annotation.getSimpleName(), compilerType)); + } catch ( ClassNotFoundException e ) { + implClass = loader.loadClass(String.format("lombok.apt.HandleANY_%s", compilerType)); + } + + Constructor<?> constructor; + + constructor = implClass.getDeclaredConstructor(); + constructor.setAccessible(true); + impl = (HandlerForCompiler<T>)constructor.newInstance(); + impl.processEnv = processEnv; + impl.roundEnv = roundEnv; + try { + impl.init(); + } catch ( Exception e ) { + error = "Exception initializing handler: " + e; + impl = null; + } + } catch ( Exception e ) { + e.printStackTrace(); + error = "You are using " + compilerType + " but a version that's changed the compiler internals. I can't work with it."; + } + } + + public AnnotationTransponder(Class<T> annotation, RoundEnvironment roundEnv, ProcessingEnvironment processEnv) { + this.processEnv = processEnv; + this.roundEnv = roundEnv; + if ( isInstanceOf(processEnv, "com.sun.tools.javac.processing.JavacProcessingEnvironment") ) { + createInstance(annotation, null, "javac"); + } else if ( isInstanceOf(processEnv, "org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl") ) { + final ClassLoader[] parentLoaders = + new ClassLoader[] { processEnv.getClass().getClassLoader(), AnnotationTransponder.class.getClassLoader() }; + + ClassLoader loader = new ClassLoader() { + @Override public Class<?> findClass(String name) throws ClassNotFoundException { + if ( name.equals(HandlerForCompiler.class.getName()) ) return HandlerForCompiler.class; + if ( name.startsWith(AnnotationTransponder.class.getPackage().getName()) ) { + byte[] data = readResource(name.replace(".", "/") + ".class"); + return defineClass(name, data, 0, data.length); + } + for ( int i = 0 ; i < parentLoaders.length ; i++ ) { + try { + return parentLoaders[i].loadClass(name); + } catch ( ClassNotFoundException e ) { + if ( i == parentLoaders.length -1 ) throw e; + } + } + + return null; + } + }; + + createInstance(annotation, loader, "ecj"); + } else { + impl = null; + this.error = "I cannot work with your compiler. I currently only support " + CURRENT_SUPPORT + ".\n" + + "This is a: " + processEnv.getClass(); + } + } + + + public void handle(Element element, T annotation) { + if ( impl == null ) { + processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, error, element); + } else { + try { + impl.handle(element, annotation); + } catch ( Exception e ) { + e.printStackTrace(); + processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Exception in JavaNext: " + e, element); + } + } + } +} diff --git a/src/lombok/apt/HandleANY_ecj.java b/src/lombok/apt/HandleANY_ecj.java new file mode 100644 index 00000000..e5a1792a --- /dev/null +++ b/src/lombok/apt/HandleANY_ecj.java @@ -0,0 +1,18 @@ +package lombok.apt; + +import java.lang.annotation.Annotation; + +import javax.lang.model.element.Element; + +import javax.tools.Diagnostic; + +public class HandleANY_ecj extends HandlerForCompiler<Annotation> { + @Override public void handle(Element element, Annotation annotation) throws Exception { + //TODO: We should find eclipse's eclipse.ini file and patch us in as a javaagent and bootclasspath/a. + //Though, we should probably use reflection to find eclipse's SWT system and generate a popup dialog for + //confirmation. + + String msg = "You'll need to install the eclipse patch. See http://lombok.github.org/ for more info."; + processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg, element); + } +} diff --git a/src/lombok/apt/HandleGetter_javac.java b/src/lombok/apt/HandleGetter_javac.java new file mode 100644 index 00000000..8432e7b0 --- /dev/null +++ b/src/lombok/apt/HandleGetter_javac.java @@ -0,0 +1,75 @@ +package lombok.apt; + +import java.lang.reflect.Modifier; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import lombok.Getter; + +import com.sun.source.tree.MethodTree; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; + +class HandleGetter_javac extends HandlerForCompiler<Getter> { + private final Trees trees; + private final Messager messager; + private JavacProcessingEnvironment env; + + HandleGetter_javac() { + this.messager = processEnv.getMessager(); + this.trees = Trees.instance(processEnv); + } + + @Override public void init() { + this.env = (JavacProcessingEnvironment)processEnv; + } + + @Override public void handle(Element element, Getter getter) { + if ( !element.getKind().isField() ) { + messager.printMessage(Diagnostic.Kind.ERROR, "@Getter is only supported on a field."); + return; + } + + TypeElement classElement = (TypeElement)element.getEnclosingElement(); + JCClassDecl javacClassTree = (JCClassDecl)trees.getTree(classElement); + + Name.Table nameTable = Name.Table.instance(env.getContext()); + TreeMaker treeMaker = TreeMaker.instance(env.getContext()); + + MethodTree getterMethod = createGetter(element, treeMaker, nameTable); + javacClassTree.defs = javacClassTree.defs.append((JCTree)getterMethod); + + messager.printMessage(Diagnostic.Kind.WARNING, "Generated a getter for this field", element); + } + + private MethodTree createGetter(Element field, TreeMaker treeMaker, Name.Table nameTable) { + JCStatement returnStatement = treeMaker.Return(treeMaker.Ident((Symbol)field)); + JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement)); + Name methodName = Name.fromString(nameTable, PKG.toGetterName(field)); + JCExpression methodType = treeMaker.Type((Type)field.asType()); + + List<JCTypeParameter> methodGenericParams = List.nil(); + List<JCVariableDecl> parameters = List.nil(); + List<JCExpression> throwsClauses = List.nil(); + JCExpression annotationMethodDefaultValue = null; + + return treeMaker.MethodDef(treeMaker.Modifiers(Modifier.PUBLIC, List.<JCAnnotation>nil()), methodName, methodType, + methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); + } +} diff --git a/src/lombok/apt/HandlerForCompiler.java b/src/lombok/apt/HandlerForCompiler.java new file mode 100644 index 00000000..7eb29385 --- /dev/null +++ b/src/lombok/apt/HandlerForCompiler.java @@ -0,0 +1,16 @@ +package lombok.apt; + +import java.lang.annotation.Annotation; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; + +public abstract class HandlerForCompiler<T extends Annotation> { + protected ProcessingEnvironment processEnv; + protected RoundEnvironment roundEnv; + + public void init() throws Exception {} + + public abstract void handle(Element element, T annotation) throws Exception; +} diff --git a/src/lombok/apt/PKG.java b/src/lombok/apt/PKG.java new file mode 100644 index 00000000..cfedf83b --- /dev/null +++ b/src/lombok/apt/PKG.java @@ -0,0 +1,77 @@ +package lombok.apt; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + +import lombok.Lombok; + + +class PKG { + static final String CURRENT_SUPPORT = "javac 1.6 and eclipse (ecj)."; + + private PKG() {} + + static boolean isInstanceOf(Object o, String className) { + if ( o == null ) return false; + return isInstanceOf(o.getClass(), className); + } + + static boolean isInstanceOf(Class<?> c, String className) { + if ( c == Object.class || c == null ) return false; + + if ( c.getName().equals(className) ) return true; + + if ( isInstanceOf(c.getSuperclass(), className) ) return true; + for ( Class<?> iface : c.getInterfaces() ) { + if ( isInstanceOf(iface, className) ) return true; + } + + return false; + } + + static byte[] readResource(String name) { + return readResource(PKG.class.getClassLoader(), name); + } + + static byte[] readResource(ClassLoader loader, String name) { + InputStream in = loader.getResourceAsStream(name); + if ( in == null ) throw Lombok.sneakyThrow(new IOException("Not found: " + name)); + + try { + return readStream(in); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } + + static String toGetterName(Element element) { + CharSequence fieldName = element.getSimpleName(); + if ( fieldName.length() == 0 ) return "get"; + + final String prefix, suffix; + + if ( element.asType().getKind() == TypeKind.BOOLEAN || "java.lang.Boolean".equals(element.asType().toString()) ) prefix = "is"; + else prefix = "get"; + + char first = fieldName.charAt(0); + if ( Character.isLowerCase(first) ) + suffix = String.format("%s%s", Character.toTitleCase(first), fieldName.subSequence(1, fieldName.length())); + else suffix = fieldName.toString(); + return String.format("%s%s", prefix, suffix); + } + + static byte[] readStream(InputStream in) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] b = new byte[65536]; + while ( true ) { + int r = in.read(b); + if ( r == -1 ) break; + if ( r > 0 ) baos.write(b, 0, r); + } + return baos.toByteArray(); + } +} diff --git a/src/lombok/apt/Processor.java b/src/lombok/apt/Processor.java new file mode 100644 index 00000000..5cae88a0 --- /dev/null +++ b/src/lombok/apt/Processor.java @@ -0,0 +1,36 @@ +package lombok.apt; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import lombok.Getter; + + +@SupportedAnnotationTypes("lombok.*") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class Processor extends AbstractProcessor { + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + for ( TypeElement typeElement : annotations ) { + if ( typeElement.getQualifiedName().contentEquals(Getter.class.getName()) ) + return handle(roundEnv, Getter.class, typeElement); + } + + return false; + } + + private <T extends Annotation> boolean handle(RoundEnvironment roundEnv, Class<T> annotation, TypeElement typeElement) { + for ( Element element : roundEnv.getElementsAnnotatedWith(typeElement) ) { + new AnnotationTransponder<T>(annotation, roundEnv, processingEnv).handle(element, element.getAnnotation(annotation)); + } + return true; + } +} |