diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-16 03:04:46 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-06-16 03:04:46 +0200 |
commit | f36be2eb01bcbff01a513d64bff2d1aba54460b1 (patch) | |
tree | 7d49ebe90cb836e054c35d2a4382be6b999ca452 /src/lombok/javac | |
parent | d78a04d74886101c81de77659b067d16cb2d0de2 (diff) | |
download | lombok-f36be2eb01bcbff01a513d64bff2d1aba54460b1.tar.gz lombok-f36be2eb01bcbff01a513d64bff2d1aba54460b1.tar.bz2 lombok-f36be2eb01bcbff01a513d64bff2d1aba54460b1.zip |
Implemented a lot of stuff for javac, but we ran into 2 major issues still to be implemented:
1. The visit mode of a lombok handler (does not trigger off of annotations, instead sees every field, method, type, and statement), needs to be coded,
2. triggering off of annotations via APT's annotation handling system skips method-local classes. We'll need to recode this via an AST visitor like we need for issue #1
Other than that, triggering off of annotations works swimmingly!
Diffstat (limited to 'src/lombok/javac')
-rw-r--r-- | src/lombok/javac/HandlerLibrary.java | 222 | ||||
-rw-r--r-- | src/lombok/javac/JavacAnnotationHandler.java | 7 | ||||
-rw-r--r-- | src/lombok/javac/JavacNode.java | 63 | ||||
-rw-r--r-- | src/lombok/javac/apt/PKG.java | 56 | ||||
-rw-r--r-- | src/lombok/javac/apt/Processor.java | 62 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleGetter_javac.java | 60 | ||||
-rw-r--r-- | src/lombok/javac/handlers/PKG.java | 34 |
7 files changed, 504 insertions, 0 deletions
diff --git a/src/lombok/javac/HandlerLibrary.java b/src/lombok/javac/HandlerLibrary.java new file mode 100644 index 00000000..6f33003f --- /dev/null +++ b/src/lombok/javac/HandlerLibrary.java @@ -0,0 +1,222 @@ +package lombok.javac; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import lombok.core.SpiLoadUtil; + + +public class HandlerLibrary { + private final Map<String, AnnotationHandlerContainer<?>> annotationHandlers = new HashMap<String, AnnotationHandlerContainer<?>>(); +// private final Collection<JavacASTVisitor> visitorHandlers = new ArrayList<JavacASTVisitor>(); + + private static class AnnotationHandlerContainer<T extends Annotation> { + private JavacAnnotationHandler<T> handler; + private Class<T> annotationClass; + + AnnotationHandlerContainer(JavacAnnotationHandler<T> handler, Class<T> annotationClass) { + this.handler = handler; + this.annotationClass = annotationClass; + } + + @SuppressWarnings("unchecked") + public void handle(JavacNode node, Object annInstance) { + handler.handle(node, (T) annInstance); + } + } + + public static HandlerLibrary load(Messager messager) { + HandlerLibrary library = new HandlerLibrary(); + loadAnnotationHandlers(messager, library); + return library; + } + + @SuppressWarnings("unchecked") + private static void loadAnnotationHandlers(Messager messager, HandlerLibrary lib) { + //No, that seemingly superfluous reference to JavacAnnotationHandler's classloader is not in fact superfluous! + Iterator<JavacAnnotationHandler> it = ServiceLoader.load(JavacAnnotationHandler.class, + JavacAnnotationHandler.class.getClassLoader()).iterator(); + while ( it.hasNext() ) { + try { + JavacAnnotationHandler<?> handler = it.next(); + Class<? extends Annotation> annotationClass = + SpiLoadUtil.findAnnotationClass(handler.getClass(), JavacAnnotationHandler.class); + AnnotationHandlerContainer<?> container = new AnnotationHandlerContainer(handler, annotationClass); + if ( lib.annotationHandlers.put(container.annotationClass.getName(), container) != null ) { + messager.printMessage(Diagnostic.Kind.WARNING, + "Duplicate handlers for annotation type: " + container.annotationClass.getName()); + } + } catch ( ServiceConfigurationError e ) { + messager.printMessage(Diagnostic.Kind.WARNING, + "Can't load Lombok annotation handler for javac: " + e); + } + } + } + + public void handleAnnotation(JavacNode node, TypeElement annotationType) { + AnnotationHandlerContainer<?> container = annotationHandlers.get(annotationType.getQualifiedName().toString()); + if ( container == null ) return; + try { + container.handle(node, createAnnotation(container.annotationClass, annotationType.getQualifiedName(), node)); + } catch ( AnnotationValueDecodeFail e ) { + node.addError(e.getMessage(), e.mirror, e.value); + } + } + + private Object createAnnotation(Class<? extends Annotation> target, Name annotationName, JavacNode node) + throws AnnotationValueDecodeFail { + AnnotationMirror mirror = fetchMirror(annotationName, node); + if ( mirror == null ) throw new AssertionError("This can't be."); + + InvocationHandler invocations = new AnnotationMirrorInvocationHandler(target, mirror); + return Proxy.newProxyInstance(target.getClassLoader(), new Class[] { target }, invocations); + } + + private static class AnnotationValueDecodeFail extends Exception { + private static final long serialVersionUID = 1L; + + AnnotationMirror mirror; + AnnotationValue value; + + AnnotationValueDecodeFail(String msg, AnnotationMirror mirror, AnnotationValue value) { + super(msg); + this.mirror = mirror; + this.value = value; + } + } + + private static class AnnotationMirrorInvocationHandler implements InvocationHandler { + private final AnnotationMirror mirror; + private final Map<String, Object> values = new HashMap<String, Object>(); + + AnnotationMirrorInvocationHandler(Class<?> target, AnnotationMirror mirror) throws AnnotationValueDecodeFail { + this.mirror = mirror; + + for ( Method m : target.getDeclaredMethods() ) { + if ( !Modifier.isPublic(m.getModifiers()) ) continue; + values.put(m.getName(), decode(m)); + } + } + + private Object decode(Method m) throws AnnotationValueDecodeFail { + for ( Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : + mirror.getElementValues().entrySet() ) { + + if ( entry.getKey().getSimpleName().contentEquals(m.getName()) ) { + AnnotationValue value = entry.getValue(); + return convert(m.getReturnType(), mirror, value, value.getValue()); + } + } + + return m.getDefaultValue(); + } + + @Override public Object invoke(Object proxy, Method method, Object[] args) { + return values.get(method.getName()); + } + + private Object convert(Class<?> expected, AnnotationMirror mirror, AnnotationValue value, Object v) throws AnnotationValueDecodeFail { + if ( expected == int.class ) { + if ( v instanceof Number ) return ((Number)v).intValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == long.class ) { + if ( v instanceof Number ) return ((Number)v).longValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == short.class ) { + if ( v instanceof Number ) return ((Number)v).shortValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == byte.class ) { + if ( v instanceof Number ) return ((Number)v).byteValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == double.class ) { + if ( v instanceof Number ) return ((Number)v).doubleValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == float.class ) { + if ( v instanceof Number ) return ((Number)v).floatValue(); + else throw new AnnotationValueDecodeFail("Expected a numeric value here", mirror, value); + } else if ( expected == char.class ) { + if ( v instanceof Character ) return v; + else throw new AnnotationValueDecodeFail("Expected a character here", mirror, value); + } else if ( expected == boolean.class ) { + if ( v instanceof Boolean ) return v; + else throw new AnnotationValueDecodeFail("Expected a boolean here", mirror, value); + } else if ( expected == String.class ) { + if ( v instanceof String ) return v; + else throw new AnnotationValueDecodeFail("Expected a String here", mirror, value); + } else if ( expected == Class.class ) { + if ( v instanceof TypeMirror ) { + try { + return Class.forName(v.toString()); + } catch ( ClassNotFoundException e ) { + throw new AnnotationValueDecodeFail( + "I can't find this class. Lombok only works well with types in the core java libraries.", + mirror, value); + } + } else throw new AnnotationValueDecodeFail("Expected a class literal here", mirror, value); + } else if ( Enum.class.isAssignableFrom(expected) ) { + if ( v instanceof VariableElement ) { + String n = ((VariableElement)v).getSimpleName().toString(); + @SuppressWarnings("unchecked") + Object enumVal = Enum.valueOf((Class<? extends Enum>)expected, n); + return enumVal; + } else throw new AnnotationValueDecodeFail("Expected an enum value here", mirror, value); + } else if ( expected.isArray() ) { + if ( v instanceof Collection<?> ) { + List<Object> convertedValues = new ArrayList<Object>(); + Class<?> componentType = expected.getComponentType(); + for ( Object innerV : (Collection<?>)v ) { + convertedValues.add(convert(componentType, mirror, value, innerV)); + } + + Object array = Array.newInstance(componentType, convertedValues.size()); + int pos = 0; + for ( Object converted : convertedValues ) Array.set(array, pos++, converted); + return array; + } else throw new AnnotationValueDecodeFail("Expected an array value here", mirror, value); +// Collection<AnnotationValue> result = (Collection<AnnotationValue>)entry.getValue().getValue(); +// return result; + } else { + throw new AssertionError("We didn't know this is even a legal annotation type: " + expected); + } + } + } + + private AnnotationMirror fetchMirror(Name lookingFor, JavacNode node) { + for ( AnnotationMirror mirror : node.getJavacAST().getAnnotationMirrors() ) { + if ( !lookingFor.contentEquals( + ((TypeElement)(mirror.getAnnotationType()).asElement()).getQualifiedName()) ) continue; + return mirror; + } + return null; + } + + public void handleType(TypeElement typeElement) { + //Later! + } + + public boolean hasHandlerFor(TypeElement annotationType) { + return annotationHandlers.containsKey(annotationType.getQualifiedName().toString()); + } +} diff --git a/src/lombok/javac/JavacAnnotationHandler.java b/src/lombok/javac/JavacAnnotationHandler.java new file mode 100644 index 00000000..67542a12 --- /dev/null +++ b/src/lombok/javac/JavacAnnotationHandler.java @@ -0,0 +1,7 @@ +package lombok.javac; + +import java.lang.annotation.Annotation; + +public interface JavacAnnotationHandler<T extends Annotation> { + void handle(JavacNode annotedElement, T annotation); +} diff --git a/src/lombok/javac/JavacNode.java b/src/lombok/javac/JavacNode.java new file mode 100644 index 00000000..2e65e1d1 --- /dev/null +++ b/src/lombok/javac/JavacNode.java @@ -0,0 +1,63 @@ +package lombok.javac; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import com.sun.source.util.Trees; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.util.Name; + +public class JavacNode { + private final Element node; + private final Messager messager; + private final JavacProcessingEnvironment env; + private final Trees trees; + + public JavacNode(Trees trees, JavacProcessingEnvironment env, Element node) { + this.trees = trees; + this.env = env; + this.node = node; + this.messager = env.getMessager(); + } + + public Element getJavacAST() { + return node; + } + + public JCClassDecl getEnclosingType() { + Element parent = node; + while ( !(parent instanceof TypeElement) ) parent = node.getEnclosingElement(); + TypeElement classElement = (TypeElement)parent; + return (JCClassDecl)trees.getTree(classElement); + } + + public Name.Table createNameTable() { + return Name.Table.instance(env.getContext()); + } + + public TreeMaker createTreeMaker() { + return TreeMaker.instance(env.getContext()); + } + + public void addError(String message) { + this.messager.printMessage(Diagnostic.Kind.ERROR, message, node); + } + + public void addError(String message, AnnotationMirror mirror, AnnotationValue value) { + this.messager.printMessage(Diagnostic.Kind.ERROR, message, node, mirror, value); + } + + public void addWarning(String message) { + this.messager.printMessage(Diagnostic.Kind.WARNING, message, node); + } + + public void addWarning(String message, AnnotationMirror mirror, AnnotationValue value) { + this.messager.printMessage(Diagnostic.Kind.WARNING, message, node, mirror, value); + } +} diff --git a/src/lombok/javac/apt/PKG.java b/src/lombok/javac/apt/PKG.java new file mode 100644 index 00000000..2ecf1c7a --- /dev/null +++ b/src/lombok/javac/apt/PKG.java @@ -0,0 +1,56 @@ +package lombok.javac.apt; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import lombok.Lombok; + + +class PKG { + 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 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/javac/apt/Processor.java b/src/lombok/javac/apt/Processor.java new file mode 100644 index 00000000..14a62367 --- /dev/null +++ b/src/lombok/javac/apt/Processor.java @@ -0,0 +1,62 @@ +package lombok.javac.apt; + +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +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.javac.HandlerLibrary; +import lombok.javac.JavacNode; + +import com.sun.source.util.Trees; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; + + +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class Processor extends AbstractProcessor { + private JavacProcessingEnvironment processingEnv; + private HandlerLibrary handlers; + private Trees trees; + + @Override public void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + if ( !(processingEnv instanceof JavacProcessingEnvironment) ) this.processingEnv = null; + else { + this.processingEnv = (JavacProcessingEnvironment) processingEnv; + handlers = HandlerLibrary.load(processingEnv.getMessager()); + } + } + + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if ( processingEnv == null ) return false; + + trees = Trees.instance(processingEnv); + + for ( TypeElement annotationType : annotations ) { + if ( !handlers.hasHandlerFor(annotationType) ) continue; + for ( Element element : roundEnv.getElementsAnnotatedWith(annotationType) ) { + System.out.println("HIGHER PING: " + element); + handlers.handleAnnotation(createNode(element), annotationType); + } + } + + for ( Element element : roundEnv.getRootElements() ) { + if ( element instanceof TypeElement ) { + handlers.handleType((TypeElement)element); + } + } + + return false; + } + + private JavacNode createNode(Element element) { + return new JavacNode(trees, processingEnv, element); + } +} diff --git a/src/lombok/javac/handlers/HandleGetter_javac.java b/src/lombok/javac/handlers/HandleGetter_javac.java new file mode 100644 index 00000000..77ce2b4f --- /dev/null +++ b/src/lombok/javac/handlers/HandleGetter_javac.java @@ -0,0 +1,60 @@ +package lombok.javac.handlers; + +import static lombok.javac.handlers.PKG.*; + +import javax.lang.model.element.Element; + +import lombok.Getter; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.source.tree.MethodTree; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +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; + +@ProviderFor(JavacAnnotationHandler.class) +public class HandleGetter_javac implements JavacAnnotationHandler<Getter> { + @Override public void handle(JavacNode node, Getter getter) { + System.out.println("PING: " + node.getJavacAST()); + if ( !node.getJavacAST().getKind().isField() ) { + node.addError("@Getter is only supported on a field."); + return; + } + + JCClassDecl javacClassTree = node.getEnclosingType(); + + int access = toJavacModifier(getter.value()); + + MethodTree getterMethod = createGetter(access, node.getJavacAST(), node.createTreeMaker(), node.createNameTable()); + javacClassTree.defs = javacClassTree.defs.append((JCTree)getterMethod); + } + + private MethodTree createGetter(int access, 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, 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(access, List.<JCAnnotation>nil()), methodName, methodType, + methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); + } +} diff --git a/src/lombok/javac/handlers/PKG.java b/src/lombok/javac/handlers/PKG.java new file mode 100644 index 00000000..4622c3ee --- /dev/null +++ b/src/lombok/javac/handlers/PKG.java @@ -0,0 +1,34 @@ +package lombok.javac.handlers; + +import java.lang.reflect.Modifier; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + +import lombok.AccessLevel; +import lombok.core.TransformationsUtil; + +class PKG { + static String toGetterName(Element field) { + CharSequence fieldName = field.getSimpleName(); + + boolean isBoolean = field.asType().getKind() == TypeKind.BOOLEAN; + + return TransformationsUtil.toGetterName(fieldName, isBoolean); + } + + static int toJavacModifier(AccessLevel accessLevel) { + switch ( accessLevel ) { + case MODULE: + case PACKAGE: + return 0; + default: + case PUBLIC: + return Modifier.PUBLIC; + case PRIVATE: + return Modifier.PRIVATE; + case PROTECTED: + return Modifier.PROTECTED; + } + } +} |