aboutsummaryrefslogtreecommitdiff
path: root/src/lombok/apt
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/apt
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/apt')
-rw-r--r--src/lombok/apt/AnnotationTransponder.java111
-rw-r--r--src/lombok/apt/HandleANY_ecj.java18
-rw-r--r--src/lombok/apt/HandleGetter_javac.java75
-rw-r--r--src/lombok/apt/HandlerForCompiler.java16
-rw-r--r--src/lombok/apt/PKG.java77
-rw-r--r--src/lombok/apt/Processor.java36
6 files changed, 333 insertions, 0 deletions
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;
+ }
+}