From f36be2eb01bcbff01a513d64bff2d1aba54460b1 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jun 2009 03:04:46 +0200 Subject: 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! --- .classpath | 2 +- .factorypath | 2 +- build.xml | 2 +- deps/lombok/spi-0.2.3-pre.jar | Bin 0 -> 23926 bytes doc/PlannedExtensions.txt | 3 + lib/lombok/spi-0.2.3-pre.jar | Bin 23926 -> 0 bytes src/lombok/apt/AnnotationTransponder.java | 111 ----------- src/lombok/apt/HandleANY_ecj.java | 17 -- src/lombok/apt/HandleGetter_javac.java | 77 -------- src/lombok/apt/HandlerForCompiler.java | 16 -- src/lombok/apt/PKG.java | 70 ------- src/lombok/apt/Processor.java | 35 ---- src/lombok/core/SpiLoadUtil.java | 37 ++++ src/lombok/eclipse/HandlerLibrary.java | 66 ++----- src/lombok/javac/HandlerLibrary.java | 222 ++++++++++++++++++++++ src/lombok/javac/JavacAnnotationHandler.java | 7 + src/lombok/javac/JavacNode.java | 63 ++++++ src/lombok/javac/apt/PKG.java | 56 ++++++ src/lombok/javac/apt/Processor.java | 62 ++++++ src/lombok/javac/handlers/HandleGetter_javac.java | 60 ++++++ src/lombok/javac/handlers/PKG.java | 34 ++++ 21 files changed, 566 insertions(+), 376 deletions(-) create mode 100644 deps/lombok/spi-0.2.3-pre.jar delete mode 100644 lib/lombok/spi-0.2.3-pre.jar delete mode 100644 src/lombok/apt/AnnotationTransponder.java delete mode 100644 src/lombok/apt/HandleANY_ecj.java delete mode 100644 src/lombok/apt/HandleGetter_javac.java delete mode 100644 src/lombok/apt/HandlerForCompiler.java delete mode 100644 src/lombok/apt/PKG.java delete mode 100644 src/lombok/apt/Processor.java create mode 100644 src/lombok/core/SpiLoadUtil.java create mode 100644 src/lombok/javac/HandlerLibrary.java create mode 100644 src/lombok/javac/JavacAnnotationHandler.java create mode 100644 src/lombok/javac/JavacNode.java create mode 100644 src/lombok/javac/apt/PKG.java create mode 100644 src/lombok/javac/apt/Processor.java create mode 100644 src/lombok/javac/handlers/HandleGetter_javac.java create mode 100644 src/lombok/javac/handlers/PKG.java diff --git a/.classpath b/.classpath index 1658c64e..f7d57bd5 100644 --- a/.classpath +++ b/.classpath @@ -13,9 +13,9 @@ - + diff --git a/.factorypath b/.factorypath index d16c72ac..5b249260 100644 --- a/.factorypath +++ b/.factorypath @@ -1,3 +1,3 @@ - + diff --git a/build.xml b/build.xml index 278c860b..6072730c 100644 --- a/build.xml +++ b/build.xml @@ -35,7 +35,7 @@ - lombok.apt.Processor + lombok.javac.apt.Processor diff --git a/deps/lombok/spi-0.2.3-pre.jar b/deps/lombok/spi-0.2.3-pre.jar new file mode 100644 index 00000000..ecd70464 Binary files /dev/null and b/deps/lombok/spi-0.2.3-pre.jar differ diff --git a/doc/PlannedExtensions.txt b/doc/PlannedExtensions.txt index ad38855e..27527d9d 100644 --- a/doc/PlannedExtensions.txt +++ b/doc/PlannedExtensions.txt @@ -17,9 +17,12 @@ Optionally you can generate a different access level by specifying the `AccessLe private @Getter(AccessLevel.PROTECTED) AnyType foo; +Don't forget to allow use on static fields! + ## @Setter Like @Getter, but creates setters. +Don't forget to allow use on static fields! ## @Data diff --git a/lib/lombok/spi-0.2.3-pre.jar b/lib/lombok/spi-0.2.3-pre.jar deleted file mode 100644 index ecd70464..00000000 Binary files a/lib/lombok/spi-0.2.3-pre.jar and /dev/null differ diff --git a/src/lombok/apt/AnnotationTransponder.java b/src/lombok/apt/AnnotationTransponder.java deleted file mode 100644 index 01a79c12..00000000 --- a/src/lombok/apt/AnnotationTransponder.java +++ /dev/null @@ -1,111 +0,0 @@ -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 The annotation class that this transponder should handle (example: Getter.class). - */ -public class AnnotationTransponder { - private HandlerForCompiler impl; - private final ProcessingEnvironment processEnv; - private final RoundEnvironment roundEnv; - private String error; - - @SuppressWarnings("unchecked") - private void createInstance(Class 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)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 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 deleted file mode 100644 index 587444d9..00000000 --- a/src/lombok/apt/HandleANY_ecj.java +++ /dev/null @@ -1,17 +0,0 @@ -package lombok.apt; - -import java.lang.annotation.Annotation; - -import javax.lang.model.element.Element; -import javax.tools.Diagnostic; - -public class HandleANY_ecj extends HandlerForCompiler { - @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 deleted file mode 100644 index bfa2746d..00000000 --- a/src/lombok/apt/HandleGetter_javac.java +++ /dev/null @@ -1,77 +0,0 @@ -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 { - 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); - } - - private MethodTree createGetter(Element field, TreeMaker treeMaker, Name.Table nameTable) { - JCStatement returnStatement = treeMaker.Return(treeMaker.Ident((Symbol)field)); - - //TODO Trab the position in the source file of the field by looking it up in the JCClassDecl, - //and copy it into the 'position' info for the Ident and Return AST Nodes. - - JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement)); - Name methodName = Name.fromString(nameTable, PKG.toGetterName(field)); - JCExpression methodType = treeMaker.Type((Type)field.asType()); - - List methodGenericParams = List.nil(); - List parameters = List.nil(); - List throwsClauses = List.nil(); - JCExpression annotationMethodDefaultValue = null; - - return treeMaker.MethodDef(treeMaker.Modifiers(Modifier.PUBLIC, List.nil()), methodName, methodType, - methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); - } -} diff --git a/src/lombok/apt/HandlerForCompiler.java b/src/lombok/apt/HandlerForCompiler.java deleted file mode 100644 index 7eb29385..00000000 --- a/src/lombok/apt/HandlerForCompiler.java +++ /dev/null @@ -1,16 +0,0 @@ -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 { - 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 deleted file mode 100644 index 2808e31e..00000000 --- a/src/lombok/apt/PKG.java +++ /dev/null @@ -1,70 +0,0 @@ -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; -import lombok.core.TransformationsUtil; - - -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 field) { - CharSequence fieldName = field.getSimpleName(); - - boolean isBoolean = field.asType().getKind() == TypeKind.BOOLEAN; - - return TransformationsUtil.toGetterName(fieldName, isBoolean); - } - - 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 deleted file mode 100644 index d63ec006..00000000 --- a/src/lombok/apt/Processor.java +++ /dev/null @@ -1,35 +0,0 @@ -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 annotations, RoundEnvironment roundEnv) { - for ( TypeElement typeElement : annotations ) { - if ( typeElement.getQualifiedName().contentEquals(Getter.class.getName()) ) - return handle(roundEnv, Getter.class, typeElement); - } - - return false; - } - - private boolean handle(RoundEnvironment roundEnv, Class annotation, TypeElement typeElement) { - for ( Element element : roundEnv.getElementsAnnotatedWith(typeElement) ) { - new AnnotationTransponder(annotation, roundEnv, processingEnv).handle(element, element.getAnnotation(annotation)); - } - return true; - } -} diff --git a/src/lombok/core/SpiLoadUtil.java b/src/lombok/core/SpiLoadUtil.java new file mode 100644 index 00000000..bf4bddf4 --- /dev/null +++ b/src/lombok/core/SpiLoadUtil.java @@ -0,0 +1,37 @@ +package lombok.core; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class SpiLoadUtil { + private SpiLoadUtil() {} + + @SuppressWarnings("unchecked") + public static Class findAnnotationClass(Class c, Class base) { + if ( c == Object.class || c == null ) return null; + for ( Type iface : c.getGenericInterfaces() ) { + if ( iface instanceof ParameterizedType ) { + ParameterizedType p = (ParameterizedType)iface; + if ( !base.equals(p.getRawType()) ) continue; + Type target = p.getActualTypeArguments()[0]; + if ( target instanceof Class ) { + if ( Annotation.class.isAssignableFrom((Class) target) ) { + return (Class) target; + } + } + + throw new ClassCastException("Not an annotation type: " + target); + } + } + + Class potential = findAnnotationClass(c.getSuperclass(), base); + if ( potential != null ) return potential; + for ( Class iface : c.getInterfaces() ) { + potential = findAnnotationClass(iface, base); + if ( potential != null ) return potential; + } + + return null; + } +} diff --git a/src/lombok/eclipse/HandlerLibrary.java b/src/lombok/eclipse/HandlerLibrary.java index f458cf45..8a7f6edc 100644 --- a/src/lombok/eclipse/HandlerLibrary.java +++ b/src/lombok/eclipse/HandlerLibrary.java @@ -4,9 +4,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -15,6 +13,7 @@ import java.util.Map; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; +import lombok.core.SpiLoadUtil; import lombok.core.TypeLibrary; import lombok.eclipse.EclipseAST.Node; @@ -60,7 +59,7 @@ public class HandlerLibrary { @SuppressWarnings("unchecked") public A createAnnotation(Class target, CompilationUnitDeclaration ast, - org.eclipse.jdt.internal.compiler.ast.Annotation node) throws EnumDecodeFail { + org.eclipse.jdt.internal.compiler.ast.Annotation node) throws AnnotationValueDecodeFail { final Map values = new HashMap(); final MemberValuePair[] pairs = node.memberValuePairs(); @@ -87,12 +86,12 @@ public class HandlerLibrary { } private Object calculateValue(MemberValuePair pair, - CompilationUnitDeclaration ast, Class type, Expression e) throws EnumDecodeFail { + CompilationUnitDeclaration ast, Class type, Expression e) throws AnnotationValueDecodeFail { if ( e instanceof Literal ) { ((Literal)e).computeConstant(); return convertConstant(pair, type, e.constant); } else if ( e instanceof ArrayInitializer ) { - if ( !type.isArray() ) throw new EnumDecodeFail(pair, "Did not expect an array here."); + if ( !type.isArray() ) throw new AnnotationValueDecodeFail(pair, "Did not expect an array here."); Class component = type.getComponentType(); Expression[] expressions = ((ArrayInitializer)e).expressions; @@ -104,19 +103,19 @@ public class HandlerLibrary { return values; } else if ( e instanceof ClassLiteralAccess ) { if ( type == Class.class ) return toClass(pair, ast, str(((ClassLiteralAccess)e).type.getTypeName())); - else throw new EnumDecodeFail(pair, "Expected a " + type + " literal."); + else throw new AnnotationValueDecodeFail(pair, "Expected a " + type + " literal."); } else if ( e instanceof NameReference ) { String s = null; if ( e instanceof SingleNameReference ) s = new String(((SingleNameReference)e).token); else if ( e instanceof QualifiedNameReference ) s = str(((QualifiedNameReference)e).tokens); if ( Enum.class.isAssignableFrom(type) ) return toEnum(pair, type, s); - throw new EnumDecodeFail(pair, "Lombok annotations must contain literals only."); + throw new AnnotationValueDecodeFail(pair, "Lombok annotations must contain literals only."); } else { - throw new EnumDecodeFail(pair, "Lombok could not decode this annotation parameter."); + throw new AnnotationValueDecodeFail(pair, "Lombok could not decode this annotation parameter."); } } - private Enum toEnum(MemberValuePair pair, Class enumType, String ref) throws EnumDecodeFail { + private Enum toEnum(MemberValuePair pair, Class enumType, String ref) throws AnnotationValueDecodeFail { int idx = ref.indexOf('.'); if ( idx > -1 ) ref = ref.substring(idx +1); Object[] enumConstants = enumType.getEnumConstants(); @@ -124,10 +123,10 @@ public class HandlerLibrary { String target = ((Enum)constant).name(); if ( target.equals(ref) ) return (Enum) constant; } - throw new EnumDecodeFail(pair, "I can't figure out which enum constant you mean."); + throw new AnnotationValueDecodeFail(pair, "I can't figure out which enum constant you mean."); } - private Class toClass(MemberValuePair pair, CompilationUnitDeclaration ast, String typeName) throws EnumDecodeFail { + private Class toClass(MemberValuePair pair, CompilationUnitDeclaration ast, String typeName) throws AnnotationValueDecodeFail { Class c; boolean fqn = typeName.indexOf('.') > -1; @@ -170,7 +169,7 @@ public class HandlerLibrary { } } - throw new EnumDecodeFail(pair, "I can't find this class. Try using the fully qualified name."); + throw new AnnotationValueDecodeFail(pair, "I can't find this class. Try using the fully qualified name."); } private Class tryClass(String name) { @@ -181,7 +180,7 @@ public class HandlerLibrary { } } - private Object convertConstant(MemberValuePair pair, Class type, Constant constant) throws EnumDecodeFail { + private Object convertConstant(MemberValuePair pair, Class type, Constant constant) throws AnnotationValueDecodeFail { int targetTypeID; boolean array = type.isArray(); if ( array ) type = type.getComponentType(); @@ -197,10 +196,10 @@ public class HandlerLibrary { else if ( type == boolean.class ) targetTypeID = TypeIds.T_boolean; else { //Enum or Class, so a constant isn't going to be very useful. - throw new EnumDecodeFail(pair, "Expected a constant of some sort here (a number or a string)"); + throw new AnnotationValueDecodeFail(pair, "Expected a constant of some sort here (a number or a string)"); } if ( !Expression.isConstantValueRepresentable(constant, constant.typeID(), targetTypeID) ) { - throw new EnumDecodeFail(pair, "I can't turn this literal into a " + type); + throw new AnnotationValueDecodeFail(pair, "I can't turn this literal into a " + type); } Object o = null; @@ -224,12 +223,12 @@ public class HandlerLibrary { return o; } - private static class EnumDecodeFail extends Exception { + private static class AnnotationValueDecodeFail extends Exception { private static final long serialVersionUID = 1L; MemberValuePair pair; - EnumDecodeFail(MemberValuePair pair, String msg) { + AnnotationValueDecodeFail(MemberValuePair pair, String msg) { super(msg); this.pair = pair; } @@ -259,7 +258,8 @@ public class HandlerLibrary { while ( it.hasNext() ) { try { EclipseAnnotationHandler handler = it.next(); - Class annotationClass = lib.findAnnotationClass(handler.getClass()); + Class annotationClass = + SpiLoadUtil.findAnnotationClass(handler.getClass(), EclipseAnnotationHandler.class); AnnotationHandlerContainer container = new AnnotationHandlerContainer(handler, annotationClass); if ( lib.annotationHandlers.put(container.annotationClass.getName(), container) != null ) { Eclipse.error("Duplicate handlers for annotation type: " + container.annotationClass.getName()); @@ -282,34 +282,6 @@ public class HandlerLibrary { } } - @SuppressWarnings("unchecked") - private Class findAnnotationClass(Class c) { - if ( c == Object.class || c == null ) return null; - for ( Type iface : c.getGenericInterfaces() ) { - if ( iface instanceof ParameterizedType ) { - ParameterizedType p = (ParameterizedType)iface; - if ( !EclipseAnnotationHandler.class.equals(p.getRawType()) ) continue; - Type target = p.getActualTypeArguments()[0]; - if ( target instanceof Class ) { - if ( Annotation.class.isAssignableFrom((Class) target) ) { - return (Class) target; - } - } - - throw new ClassCastException("Not an annotation type: " + target); - } - } - - Class potential = findAnnotationClass(c.getSuperclass()); - if ( potential != null ) return potential; - for ( Class iface : c.getInterfaces() ) { - potential = findAnnotationClass(iface); - if ( potential != null ) return potential; - } - - return null; - } - public void handle(CompilationUnitDeclaration ast, EclipseAST.Node annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation) { TypeResolver resolver = new TypeResolver(typeLibrary, annotationNode.top()); @@ -321,7 +293,7 @@ public class HandlerLibrary { Object annInstance; try { annInstance = createAnnotation(container.annotationClass, ast, annotation); - } catch ( EnumDecodeFail e ) { + } catch ( AnnotationValueDecodeFail e ) { annotationNode.addError(e.getMessage(), e.pair.sourceStart, e.pair.sourceEnd); return; } 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> annotationHandlers = new HashMap>(); +// private final Collection visitorHandlers = new ArrayList(); + + private static class AnnotationHandlerContainer { + private JavacAnnotationHandler handler; + private Class annotationClass; + + AnnotationHandlerContainer(JavacAnnotationHandler handler, Class 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 it = ServiceLoader.load(JavacAnnotationHandler.class, + JavacAnnotationHandler.class.getClassLoader()).iterator(); + while ( it.hasNext() ) { + try { + JavacAnnotationHandler handler = it.next(); + Class 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 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 values = new HashMap(); + + 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 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)expected, n); + return enumVal; + } else throw new AnnotationValueDecodeFail("Expected an enum value here", mirror, value); + } else if ( expected.isArray() ) { + if ( v instanceof Collection ) { + List convertedValues = new ArrayList(); + 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 result = (Collection)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 { + 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 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 { + @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 methodGenericParams = List.nil(); + List parameters = List.nil(); + List throwsClauses = List.nil(); + JCExpression annotationMethodDefaultValue = null; + + return treeMaker.MethodDef(treeMaker.Modifiers(access, List.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; + } + } +} -- cgit