From 1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jun 2009 22:26:30 +0200 Subject: Initial commit. As a proof of concept, it already works in javac and eclipse! --- .classpath | 15 + .gitignore | 4 + .project | 17 ++ .settings/org.eclipse.jdt.core.prefs | 12 + build.xml | 49 ++++ contrib/asm-3.1-javadoc.zip | Bin 0 -> 620640 bytes contrib/asm-3.1-src.zip | Bin 0 -> 336609 bytes ...eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar | Bin 0 -> 362141 bytes ...t.apt.pluggable.core_1.0.200.v20090526-2130.jar | Bin 0 -> 30999 bytes ...pse.jdt.compiler.apt_1.0.200.v20090528-1135.jar | Bin 0 -> 153765 bytes deps/org.eclipse.jdt.core_3.5.0.v_963.jar | Bin 0 -> 4471784 bytes doc/PlannedExtensions.txt | 45 +++ lib/asm-3.1.jar | Bin 0 -> 43035 bytes original_panno/com/hanhuy/panno/Property.java | 71 +++++ .../hanhuy/panno/processing/ASTTreeToSource.java | 320 +++++++++++++++++++++ .../hanhuy/panno/processing/PropertyProcessor.java | 293 +++++++++++++++++++ src/java/lombok/ClassLoaderWorkaround.java | 74 +++++ src/lombok/AccessLevel.java | 5 + src/lombok/Getter.java | 12 + src/lombok/Lombok.java | 31 ++ src/lombok/agent/eclipse/EclipseParserPatcher.java | 47 +++ .../agent/eclipse/EclipseParserTransformer.java | 83 ++++++ .../TransformCompilationUnitDeclaration.java | 55 ++++ src/lombok/apt/AnnotationTransponder.java | 111 +++++++ src/lombok/apt/HandleANY_ecj.java | 18 ++ src/lombok/apt/HandleGetter_javac.java | 75 +++++ src/lombok/apt/HandlerForCompiler.java | 16 ++ src/lombok/apt/PKG.java | 77 +++++ src/lombok/apt/Processor.java | 36 +++ 29 files changed, 1466 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 build.xml create mode 100644 contrib/asm-3.1-javadoc.zip create mode 100644 contrib/asm-3.1-src.zip create mode 100644 deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar create mode 100644 deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar create mode 100644 deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar create mode 100644 deps/org.eclipse.jdt.core_3.5.0.v_963.jar create mode 100644 doc/PlannedExtensions.txt create mode 100644 lib/asm-3.1.jar create mode 100644 original_panno/com/hanhuy/panno/Property.java create mode 100644 original_panno/com/hanhuy/panno/processing/ASTTreeToSource.java create mode 100644 original_panno/com/hanhuy/panno/processing/PropertyProcessor.java create mode 100644 src/java/lombok/ClassLoaderWorkaround.java create mode 100644 src/lombok/AccessLevel.java create mode 100644 src/lombok/Getter.java create mode 100644 src/lombok/Lombok.java create mode 100644 src/lombok/agent/eclipse/EclipseParserPatcher.java create mode 100644 src/lombok/agent/eclipse/EclipseParserTransformer.java create mode 100644 src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java create mode 100644 src/lombok/apt/AnnotationTransponder.java create mode 100644 src/lombok/apt/HandleANY_ecj.java create mode 100644 src/lombok/apt/HandleGetter_javac.java create mode 100644 src/lombok/apt/HandlerForCompiler.java create mode 100644 src/lombok/apt/PKG.java create mode 100644 src/lombok/apt/Processor.java diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..f54ce91b --- /dev/null +++ b/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e811f014 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin +build +dist + diff --git a/.project b/.project new file mode 100644 index 00000000..628b6001 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + lombok + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..fbe9d485 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Thu Jun 04 03:58:11 CEST 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..f2d1b635 --- /dev/null +++ b/build.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + lombok.apt.Processor + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/asm-3.1-javadoc.zip b/contrib/asm-3.1-javadoc.zip new file mode 100644 index 00000000..91ce1569 Binary files /dev/null and b/contrib/asm-3.1-javadoc.zip differ diff --git a/contrib/asm-3.1-src.zip b/contrib/asm-3.1-src.zip new file mode 100644 index 00000000..4ad32d54 Binary files /dev/null and b/contrib/asm-3.1-src.zip differ diff --git a/deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar b/deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar new file mode 100644 index 00000000..e42dc67f Binary files /dev/null and b/deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar differ diff --git a/deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar b/deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar new file mode 100644 index 00000000..73286f15 Binary files /dev/null and b/deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar differ diff --git a/deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar b/deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar new file mode 100644 index 00000000..5a66398a Binary files /dev/null and b/deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar differ diff --git a/deps/org.eclipse.jdt.core_3.5.0.v_963.jar b/deps/org.eclipse.jdt.core_3.5.0.v_963.jar new file mode 100644 index 00000000..0db68569 Binary files /dev/null and b/deps/org.eclipse.jdt.core_3.5.0.v_963.jar differ diff --git a/doc/PlannedExtensions.txt b/doc/PlannedExtensions.txt new file mode 100644 index 00000000..d6c5ffd7 --- /dev/null +++ b/doc/PlannedExtensions.txt @@ -0,0 +1,45 @@ +Planned lombok features +======================= + +## @Getter + +Put on any field; like so: + + private @Getter AnyType foo; + +This will generate the following method: + + public AnyType getFoo() { + return foo; + } + +Optionally you can generate a different access level by specifying the `AccessLevel` in the annotation, like so: + + private @Getter(AccessLevel.PROTECTED) AnyType foo; + +## @Setter + +Like @Getter, but creates setters. + +## @Data + +Creates getters, setters (for non-final fields), toString, equals, and hashCode, as well as a constructor, or, if you wish, +a 'static factory method'. + +## @Property + +## @AutoClose + +## @Synchronized + +## @Generator + +## @SneakyThrows + +# Maybes: + +## @RunInEDT + +## @SaneEquals + +## List Comprehensions diff --git a/lib/asm-3.1.jar b/lib/asm-3.1.jar new file mode 100644 index 00000000..b3baf3fe Binary files /dev/null and b/lib/asm-3.1.jar differ diff --git a/original_panno/com/hanhuy/panno/Property.java b/original_panno/com/hanhuy/panno/Property.java new file mode 100644 index 00000000..5e02a536 --- /dev/null +++ b/original_panno/com/hanhuy/panno/Property.java @@ -0,0 +1,71 @@ +/* + * Copyright 2007 Perry Nguyen Licensed under the Apache + * License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.hanhuy.panno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A simple javabean property annotation. Used to generate get/set pairs for + * field members automatically. Valid options are: 'name', 'readOnly', + * 'writeOnly', 'useGet', 'preCallMethod', and 'preCallThrows'. + *

+ *'name' may be used to set an alternative name for the property instead of + * defaulting to the field's name. + *

+ *'readOnly' and 'writeOnly' may be set to true to create only a getter or a + * setter, respectively. + *

+ *'useGet' is used to generate a getX instead of isX method if the property is + * a boolean. + *

+ *'preCallMethod' is the name of the method to be invoked prior to setting the + * actual property value. It may be used for validation, or firing off + * PropertyChangeEvents. This method will be called as + * preCallMethod(this, String propertyName, oldValue, newValue); + * since this occurs at compile time, there is no interface, so the types are + * entirely up to you to choose and make work. The method may also be any + * method, so long as it's accessible to the bean. Thus, + * preCallMethod=anotherObject.validateProperty would be ok, so + * long as anotherObject is a field within the bean. The object + * navigation leading up to the preCallMethod cannot contain any method + * invocations; e.g. + * preCallMethod=someBean.someMethod().myPreCallMethod would be + * illegal and cause an error. + *

+ *'preCallThrows' specifies what exceptions can possibly be thrown by the + * preCallMethod. Any non-RuntimeExceptions must be specified here, or + * else it will result in a compile-time error. This information cannot be + * determined reflectively, because at compile-time, there is no reflection + * (reflection is for runtime use). All exception names must be imported and + * they may not be referred by their qualified name, e.g. "Exception", not + * "java.lang.Exception" + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface Property { + String name() default ""; + + boolean readOnly() default false; + + boolean writeOnly() default false; + + boolean useGet() default false; + + String[] preCallThrows() default { /* nothing */}; + + String preCallMethod() default ""; +} diff --git a/original_panno/com/hanhuy/panno/processing/ASTTreeToSource.java b/original_panno/com/hanhuy/panno/processing/ASTTreeToSource.java new file mode 100644 index 00000000..2e7dc641 --- /dev/null +++ b/original_panno/com/hanhuy/panno/processing/ASTTreeToSource.java @@ -0,0 +1,320 @@ +/* + * Copyright 2007 Perry Nguyen Licensed under the Apache + * License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.hanhuy.panno.processing; + +import java.util.Map; +import java.util.Stack; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.Modifier; +import javax.tools.Diagnostic; + +import com.hanhuy.panno.Property; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreeScanner; + +/** + * Old attempt to get the annotation working, have a better approach now. + */ +@Deprecated +class ASTTreeToSource extends TreeScanner { + public final static String PROPERTY_ANNOTATION_FULL = "com.hanhuy.panno.Property"; + public final static String PROPERTY_ANNOTATION = "Property"; + + private final Stack currentClassNameStack = new Stack(); + private final Messager messager; + private final Map fieldMap; + private String currentClassName; + private String currentField; + private String currentType; + + ASTTreeToSource(Map fieldMap, Messager messager) { + this.fieldMap = fieldMap; + this.messager = messager; + } + + /** + * Handle initializer blocks, both static and non. + */ + @Override + public Void visitBlock(BlockTree node, StringBuilder p) { + p.append("\n"); + if ( node.isStatic() ) p.append("static "); + p.append("{\n"); + for ( StatementTree st : node.getStatements() ) { + p.append(st); + p.append(";"); + } + p.append("\n}\n"); + return null; + } + + /** + * Detect our annotation and add accessors if necessary. + */ + @Override + public Void visitAnnotation(AnnotationTree node, StringBuilder p) { + String anno = ((IdentifierTree)node.getAnnotationType()).getName().toString(); + if ( anno != null && PROPERTY_ANNOTATION.equals(anno.trim()) ) { + Property pAnno = fieldMap.get(currentClassName + "." + currentField); + + String propName = currentField; + if ( !"".equals(pAnno.name()) ) propName = pAnno.name(); + propName = PropertyProcessor.ucfirst(propName); + + if ( pAnno.writeOnly() && pAnno.readOnly() ) { + messager.printMessage(Diagnostic.Kind.ERROR, "@Property's writeOnly and readOnly may not both be true"); + } + + if ( !"boolean".equals(currentType) && pAnno.useGet() ) { + messager.printMessage(Diagnostic.Kind.WARNING, "@Property's useGet is only for booleans"); + } + + if ( !pAnno.writeOnly() ) { + // generate a getter + p.append("\n"); + p.append("public "); + p.append(currentType); + if ( !"boolean".equals(currentType) || pAnno.useGet() ) p.append(" get"); + else p.append(" is"); + p.append(propName); + p.append("() { return "); + p.append(currentField); + p.append("; }"); + } + if ( !pAnno.readOnly() ) { + // generate a setter + p.append("\n"); + p.append("public void set"); + p.append(propName); + p.append("("); + p.append(currentType); + p.append(" "); + p.append(currentField); + p.append(") { this."); + p.append(currentField); + p.append(" = "); + p.append(currentField); + p.append("; }"); + } + } + return null; + } + + /** + * Gather file information. Then continue to visit classes. + */ + @Override + public Void visitCompilationUnit(CompilationUnitTree node, StringBuilder p) { + new Exception("Visit CUT: " + node.getSourceFile()).printStackTrace(); + p.append("// Generated from: "); + p.append(node.getSourceFile()); + p.append("\n\npackage "); + p.append(node.getPackageName()); + p.append(";\n\n"); + + for ( ImportTree it : node.getImports() ) { + if ( PROPERTY_ANNOTATION_FULL.equals(it.getQualifiedIdentifier().toString().trim()) ) { + continue; + } + p.append("import "); + if ( it.isStatic() ) p.append("static "); + p.append(it.getQualifiedIdentifier()); + p.append(";\n"); + } + super.visitCompilationUnit(node, p); + return null; + } + + private void pushClass(String className) { + currentClassName = className; + currentClassNameStack.push(currentClassName); + } + + private void popClass() { + currentClassNameStack.pop(); + if ( currentClassNameStack.size() > 0 ) currentClassName = currentClassNameStack.peek(); + } + + /** + * Reproduce class declarations, and go on to handle methods, blocks and + * variables. + */ + @Override + public Void visitClass(ClassTree node, StringBuilder p) { + pushClass(node.getSimpleName().toString()); + + // Check to see if this is an enum + // I have no idea what will happen if we encounter an AST + // implementation that is not Sun's (or if this has changed in java7) + if ( node instanceof com.sun.tools.javac.tree.JCTree.JCClassDecl ) { + com.sun.tools.javac.tree.JCTree.JCClassDecl jcd = (com.sun.tools.javac.tree.JCTree.JCClassDecl)node; + if ( (jcd.mods.flags & 16384L) != 0L ) { + p.append(node.getModifiers()); + p.append(" enum "); + p.append(currentClassName); + appendEnumBody(jcd.defs, p); + + popClass(); + + return null; + } + } + + p.append("\n"); + p.append(node.getModifiers()); + p.append(" class "); + p.append(currentClassName); + if ( node.getTypeParameters().size() > 0 ) { + p.append("<"); + p.append(node.getTypeParameters()); + p.append(">"); + } + if ( node.getExtendsClause() != null ) { + p.append("\nextends "); + p.append(node.getExtendsClause()); + } + if ( node.getImplementsClause().size() > 0 ) { + p.append("\nimplements "); + p.append(node.getImplementsClause()); + } + + p.append("\n{"); + super.visitClass(node, p); + p.append("\n}\n"); + + popClass(); + + return null; + } + + /** + * Enums appear to be a special case. We must reconstitute its source in a + * different manner from the rest. Need to remove any 'super()' statements + * and print the lines appropriately. + */ + public void appendEnumBody(com.sun.tools.javac.util.List list, StringBuilder p) { + p.append("{\n"); + boolean flag = true; + + for ( com.sun.tools.javac.util.List list1 = list; list1.nonEmpty(); list1 = list1.tail ) { + + if ( !isEnumerator(list1.head) ) continue; + + if ( !flag ) p.append(",\n"); + + p.append(list1.head); + flag = false; + } + + p.append(";\n"); + for ( com.sun.tools.javac.util.List list2 = list; list2.nonEmpty(); list2 = list2.tail ) { + if ( !isEnumerator(list2.head) ) { + if ( list2.head instanceof MethodTree ) visitMethod((MethodTree)list2.head, p); + else p.append(list2.head); + p.append("\n"); + } + } + + p.append("}"); + } + + private boolean isEnumerator(com.sun.tools.javac.tree.JCTree jctree) { + return jctree.tag == 5 && (((com.sun.tools.javac.tree.JCTree.JCVariableDecl)jctree).mods.flags & 16384L) != 0L; + } + + /** + * Reconstitute methods. We could just do node.toString() here, but + * constructors need to be renamed from <init> to the class name, so + * handle all methods. + */ + @Override + public Void visitMethod(MethodTree node, StringBuilder p) { + p.append("\n"); + p.append(node.getModifiers()); + p.append(" "); + if ( node.getReturnType() != null ) p.append(node.getReturnType()); + if ( node.getTypeParameters().size() > 0 ) { + p.append(" <"); + p.append(node.getTypeParameters()); + p.append(">"); + } + p.append(" "); + if ( node.getName() != null && "".equals(node.getName().toString().trim()) ) p.append(currentClassName); + else p.append(node.getName()); + + p.append("("); + p.append(node.getParameters()); + p.append(")"); + if ( node.getThrows().size() > 0 ) { + p.append("\nthrows "); + p.append(node.getThrows()); + } + + p.append(" {\n"); + // this needs to be done for enums, otherwise we'll get a compile error + // if we didn't need this case, we'd just do p.append(node.getBody()) + for ( StatementTree st : node.getBody().getStatements() ) { + if ( "super()".equals(st.toString().trim()) ) continue; + p.append(st); + if ( p.charAt(p.length() - 1) != ';' ) p.append(";\n"); + } + p.append("\n}\n"); + return null; + } + + /** + * Reconstitute class fields. While doing so, search for our annotation to + * perform processing. + */ + @Override + public Void visitVariable(VariableTree node, StringBuilder p) { + currentField = node.getName().toString(); + currentType = node.getType().toString(); + p.append("\n"); + + for ( AnnotationTree a : node.getModifiers().getAnnotations() ) { + String anno = ((IdentifierTree)a.getAnnotationType()).getName().toString(); + if ( !PROPERTY_ANNOTATION.equals(anno) ) { + p.append(a); + p.append("\n"); + } + } + + for ( Modifier m : node.getModifiers().getFlags() ) { + p.append(m.toString()); + p.append(" "); + } + p.append(" "); + p.append(currentType); + p.append(" "); + p.append(currentField); + + if ( node.getInitializer() != null ) { + p.append(" = "); + p.append(node.getInitializer()); + } + p.append(";"); + super.visitVariable(node, p); + return null; + } +} diff --git a/original_panno/com/hanhuy/panno/processing/PropertyProcessor.java b/original_panno/com/hanhuy/panno/processing/PropertyProcessor.java new file mode 100644 index 00000000..e7bb4826 --- /dev/null +++ b/original_panno/com/hanhuy/panno/processing/PropertyProcessor.java @@ -0,0 +1,293 @@ +/* + * Copyright 2007 Perry Nguyen Licensed under the Apache + * License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.hanhuy.panno.processing; + +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +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.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic; + +import com.hanhuy.panno.Property; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.Trees; + +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedAnnotationTypes( { "com.hanhuy.panno.Property" }) +public class PropertyProcessor extends AbstractProcessor { + + private Messager messager; + + /** + * Annotation processing entry point. Currently, we only support javac 6 + * from Sun's JDK. Other IDE support will have to follow. + */ + public boolean process(Set annotations, RoundEnvironment env) { + com.sun.tools.javac.util.Context ctx = null; + + if ( processingEnv instanceof com.sun.tools.javac.processing.JavacProcessingEnvironment ) { + ctx = ((com.sun.tools.javac.processing.JavacProcessingEnvironment)processingEnv).getContext(); + } else { + messager.printMessage(Diagnostic.Kind.ERROR, "@Property processing is not supported on this compiler."); + } + + Trees trees = Trees.instance(processingEnv); + messager = processingEnv.getMessager(); + + Set annoElements = env.getElementsAnnotatedWith(Property.class); + + for ( Element e : annoElements ) { + processAnnotation(trees, ctx, e, e.getAnnotation(Property.class)); + } + return true; + } + + private void processAnnotation(Trees trees, com.sun.tools.javac.util.Context ctx, Element e, Property p) { + TypeElement type = (TypeElement)findEnclosingType(e); + + if ( type == null ) { + messager.printMessage(Diagnostic.Kind.ERROR, "@Property annotated element is not a member field.", e); + } + ClassTree cls = trees.getTree(type); + com.sun.tools.javac.tree.JCTree.JCClassDecl cd = (com.sun.tools.javac.tree.JCTree.JCClassDecl)cls; + Object t = trees.getTree(e); + if ( !(t instanceof VariableTree) ) { + messager.printMessage(Diagnostic.Kind.ERROR, "@Property can only be annotated on a member field", e); + return; + } + + com.sun.tools.javac.util.Name.Table tab = com.sun.tools.javac.util.Name.Table.instance(ctx); + + com.sun.tools.javac.tree.TreeMaker maker = com.sun.tools.javac.tree.TreeMaker.instance(ctx); + + MethodTree m; + String[] pcts = p.preCallThrows(); + String pcm = p.preCallMethod(); + if ( p.readOnly() && ((pcm != null && !"".equals(pcm.trim())) || (pcts != null && pcts.length > 0)) ) { + messager.printMessage(Diagnostic.Kind.ERROR, "preCallMethod and/or preCallThrows can" + + "not be used with readOnly", e, getAnnotationMirror(e)); + } + if ( p.writeOnly() && p.useGet() ) { + messager.printMessage(Diagnostic.Kind.ERROR, "useGet is only for use with readable boolean properties", e, + getAnnotationMirror(e)); + } + if ( !p.readOnly() ) { + m = createSetter(e, p, maker, tab, trees); + cd.defs = cd.defs.append((com.sun.tools.javac.tree.JCTree)m); + } + if ( !p.writeOnly() ) { + m = createGetter(e, p, maker, tab); + cd.defs = cd.defs.append((com.sun.tools.javac.tree.JCTree)m); + } + } + + private String getPropertyName(Element e, Property p) { + String pname = p.name(); + if ( pname == null || "".equals(p.name().trim()) ) pname = e.getSimpleName().toString(); + return pname; + } + + @SuppressWarnings("unchecked") + private MethodTree createGetter(Element e, Property p, com.sun.tools.javac.tree.TreeMaker maker, + com.sun.tools.javac.util.Name.Table tab) { + + String pname = getPropertyName(e, p); + + com.sun.tools.javac.util.List nil = com.sun.tools.javac.util.List.nil(); + + com.sun.tools.javac.util.List statements = com.sun.tools.javac.util.List.of(maker.Return(maker + .Ident((com.sun.tools.javac.code.Symbol.VarSymbol)e))); + + String getterPrefix = "get"; + // check if boolean, use "is", otherwise "get" if useGet + if ( e.asType().getKind() == TypeKind.BOOLEAN ) { + if ( !p.useGet() ) getterPrefix = "is"; + } else { + if ( p.useGet() ) messager.printMessage(Diagnostic.Kind.WARNING, + "@Property(useGet=true) is for boolean properties", e); + } + + com.sun.tools.javac.util.Name n = com.sun.tools.javac.util.Name.fromString(tab, getterPrefix + ucfirst(pname)); + + MethodTree m = maker.MethodDef(maker.Modifiers(1L, nil), n, maker.Type((com.sun.tools.javac.code.Type)e + .asType()), nil, nil, nil, maker.Block(0, statements), null); + + return m; + } + + @SuppressWarnings("unchecked") + private MethodTree createSetter(Element e, Property p, com.sun.tools.javac.tree.TreeMaker maker, + com.sun.tools.javac.util.Name.Table tab, Trees trees) { + +// String pname = getPropertyName(e, p); + int pos = getAnnotationPosition(e, trees); + + com.sun.tools.javac.util.List nil = com.sun.tools.javac.util.List.nil(); + + com.sun.tools.javac.util.Name vn = com.sun.tools.javac.util.Name.fromString(tab, "__panno_Generated_" + + e.getSimpleName().toString()); + + com.sun.tools.javac.tree.JCTree.JCVariableDecl param = maker.Param(vn, (com.sun.tools.javac.code.Type)e + .asType(), null); + + com.sun.tools.javac.util.List statements = com.sun.tools.javac.util.List + .of(maker.Assignment((com.sun.tools.javac.code.Symbol.VarSymbol)e, maker.Ident(param))); + +// com.sun.tools.javac.util.Name n = com.sun.tools.javac.util.Name.fromString(tab, "set" + ucfirst(pname)); + + com.sun.tools.javac.util.List throwList = nil; + MethodInvocationTree mit = createPreCall(e, p, param, maker, tab, pos); + + if ( mit != null ) { + statements = statements.prepend(maker.Exec((com.sun.tools.javac.tree.JCTree.JCMethodInvocation)mit)); + if ( p.preCallThrows() != null && p.preCallThrows().length > 0 ) { + for ( String thrown : p.preCallThrows() ) { + com.sun.tools.javac.util.Name tn = com.sun.tools.javac.util.Name.fromString(tab, thrown); + + com.sun.tools.javac.code.Symbol.ClassSymbol clssym = new com.sun.tools.javac.code.Symbol.ClassSymbol( + 0, tn, null); + throwList = throwList.append(maker.Type(clssym.type).setPos(pos)); + // setpos to munge line number + } + } + + } else if ( p.preCallThrows() != null && p.preCallThrows().length > 0 ) { + messager.printMessage(Diagnostic.Kind.ERROR, "preCallThrows is only to be used with preCallMethod", e, + getAnnotationMirror(e)); + } + + com.sun.tools.javac.tree.JCTree.JCMethodDecl m = null/*maker.MethodDef(maker.Modifiers(1L, nil), n, maker + .Type(com.sun.tools.javac.code.Symtab.voidType), nil, com.sun.tools.javac.util.List.of(param), + throwList, maker.Block(0, statements), null)*/; + + // munge line numbers +// m.mods.pos = pos; +// m.pos = pos; + return m; + } + + private MethodInvocationTree createPreCall(Element e, Property p, + com.sun.tools.javac.tree.JCTree.JCVariableDecl newParam, com.sun.tools.javac.tree.TreeMaker maker, + com.sun.tools.javac.util.Name.Table tab, int pos) { + String pcm = p.preCallMethod(); + String pname = getPropertyName(e, p); + pname = lcfirst(pname); + + com.sun.tools.javac.tree.JCTree.JCMethodInvocation m = null; + if ( pcm == null || "".equals(pcm) ) return m; + String[] parts = pcm.split("\\."); + com.sun.tools.javac.tree.JCTree.JCExpression expr = null; + Element enclosing = findEnclosingType(e); + + String lastPart = null; + if ( parts.length > 1 ) { + // A simple object navigation path is named. + // I'm too lazy to support/figure out method invocations here, + // do plain member selection only + for ( int i = 0, j = parts.length; i < j; i++ ) { + if ( parts[i].endsWith(")") ) { + messager.printMessage(Diagnostic.Kind.ERROR, "Cannot call a method in graph to preCallMethod", e, + getAnnotationMirror(e)); + } + + com.sun.tools.javac.util.Name partName = com.sun.tools.javac.util.Name.fromString(tab, parts[i]); + + if ( lastPart != null ) { + + expr = maker.Select(expr, partName); + + } else { + + com.sun.tools.javac.tree.JCTree.JCVariableDecl param = maker.Param(partName, null, null); + expr = maker.Ident(param); + } + // this is how we get the correct line number + expr.pos = pos; + + lastPart = parts[i]; + } + } else { + // A method is named +// com.sun.tools.javac.util.Name name = com.sun.tools.javac.util.Name.fromString(tab, parts[0]); + + com.sun.tools.javac.code.Symbol.MethodSymbol msym = null/*new com.sun.tools.javac.code.Symbol.MethodSymbol(0, + name, com.sun.tools.javac.code.Symtab.voidType, (com.sun.tools.javac.code.Symbol)enclosing)*/; + + expr = maker.Ident(msym); + // this is how we get the correct line number when there's an error. + expr.pos = pos; + } + + m = maker.Apply(null, expr, com.sun.tools.javac.util.List.of(maker + .This((com.sun.tools.javac.code.Type)enclosing.asType()), maker.Literal(pname), maker + .Ident((com.sun.tools.javac.code.Symbol)e), maker.Ident(newParam))); + + // this is required, I guess... + m.setType(new com.sun.tools.javac.code.Type.MethodType(null, null, null, null)); + + return m; + } + + private int getAnnotationPosition(Element e, Trees trees) { + return (int)trees.getSourcePositions().getStartPosition(trees.getPath(e).getCompilationUnit(), + trees.getTree(e, getAnnotationMirror(e))); + } + + /** + * Returns the enclosing type if found. (Should return a TypeElement object) + */ + static Element findEnclosingType(Element e) { + return e == null || e instanceof TypeElement ? e : e.getEnclosingElement(); + } + + /** + * Uppercase the first character of the string. Named after the ucfirst + * function in Perl. + */ + static String ucfirst(String name) { + String first = name.substring(0, 1).toUpperCase(); + return first + name.substring(1); + } + + static String lcfirst(String name) { + String first = name.substring(0, 1).toLowerCase(); + return first + name.substring(1); + } + + /** + * Return the Property AnnotationMirror for this element. + */ + static AnnotationMirror getAnnotationMirror(Element e) { + List annos = e.getAnnotationMirrors(); + for ( AnnotationMirror am : annos ) { + DeclaredType type = am.getAnnotationType(); + String name = type.asElement().getSimpleName().toString(); + if ( name != null && "Property".equals(name.trim()) ) return am; + } + throw new IllegalStateException("Element must have a @Property annotation"); + } +} 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: + * + *

public void run() {
+	 *     throw sneakyThrow(new IOException("You don't need to catch me!"));
+	 * }
+ * + * 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 always throws an exception! + */ + public static RuntimeException sneakyThrow(Throwable t) { + if ( t == null ) throw new NullPointerException("t"); + Lombok.sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static 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 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 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 { + @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 { + 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 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 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 { + 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 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; + } +} -- cgit