aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@tipit.to>2009-06-08 22:26:30 +0200
committerReinier Zwitserloot <reinier@tipit.to>2009-06-08 22:26:30 +0200
commit1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce (patch)
tree29de8d12d774f026c0f317e229532befaeb2d096
downloadlombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.tar.gz
lombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.tar.bz2
lombok-1677c4a52a0aea1b955f7c2c7d096903d4a8c5ce.zip
Initial commit. As a proof of concept, it already works in javac and eclipse!
-rw-r--r--.classpath15
-rw-r--r--.gitignore4
-rw-r--r--.project17
-rw-r--r--.settings/org.eclipse.jdt.core.prefs12
-rw-r--r--build.xml49
-rw-r--r--contrib/asm-3.1-javadoc.zipbin0 -> 620640 bytes
-rw-r--r--contrib/asm-3.1-src.zipbin0 -> 336609 bytes
-rw-r--r--deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jarbin0 -> 362141 bytes
-rw-r--r--deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jarbin0 -> 30999 bytes
-rw-r--r--deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jarbin0 -> 153765 bytes
-rw-r--r--deps/org.eclipse.jdt.core_3.5.0.v_963.jarbin0 -> 4471784 bytes
-rw-r--r--doc/PlannedExtensions.txt45
-rw-r--r--lib/asm-3.1.jarbin0 -> 43035 bytes
-rw-r--r--original_panno/com/hanhuy/panno/Property.java71
-rw-r--r--original_panno/com/hanhuy/panno/processing/ASTTreeToSource.java320
-rw-r--r--original_panno/com/hanhuy/panno/processing/PropertyProcessor.java293
-rw-r--r--src/java/lombok/ClassLoaderWorkaround.java74
-rw-r--r--src/lombok/AccessLevel.java5
-rw-r--r--src/lombok/Getter.java12
-rw-r--r--src/lombok/Lombok.java31
-rw-r--r--src/lombok/agent/eclipse/EclipseParserPatcher.java47
-rw-r--r--src/lombok/agent/eclipse/EclipseParserTransformer.java83
-rw-r--r--src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java55
-rw-r--r--src/lombok/apt/AnnotationTransponder.java111
-rw-r--r--src/lombok/apt/HandleANY_ecj.java18
-rw-r--r--src/lombok/apt/HandleGetter_javac.java75
-rw-r--r--src/lombok/apt/HandlerForCompiler.java16
-rw-r--r--src/lombok/apt/PKG.java77
-rw-r--r--src/lombok/apt/Processor.java36
29 files changed, 1466 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 00000000..f54ce91b
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="lib" path="lib/asm-3.1.jar" sourcepath="contrib/asm-3.1-src.zip">
+ <attributes>
+ <attribute name="javadoc_location" value="jar:platform:/resource/lombok/contrib/asm-3.1-javadoc.zip!/"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="lib" path="deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar"/>
+ <classpathentry kind="lib" path="deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar"/>
+ <classpathentry kind="lib" path="deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar"/>
+ <classpathentry kind="lib" path="deps/org.eclipse.jdt.core_3.5.0.v_963.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>lombok</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
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 @@
+<project name="lombok" default="dist">
+ <property name="build.compiler" value="javac1.6" />
+ <path id="deps.path">
+ <fileset dir="deps">
+ <include name="**/*.jar" />
+ </fileset>
+ </path>
+ <path id="libs.path">
+ <fileset dir="lib">
+ <include name="**/*.jar" />
+ </fileset>
+ </path>
+
+ <target name="clean">
+ <delete dir="build" quiet="true" />
+ <delete dir="dist" quiet="true" />
+ </target>
+
+ <target name="compile">
+ <mkdir dir="build" />
+ <javac srcdir="src" debug="on" destdir="build">
+ <classpath refid="deps.path" />
+ <classpath refid="libs.path" />
+ </javac>
+ <mkdir dir="build/META-INF" />
+ <mkdir dir="build/META-INF/services" />
+ <echo file="build/META-INF/services/javax.annotation.processing.Processor">lombok.apt.Processor</echo>
+ </target>
+
+ <target name="unpackLibs">
+ <unjar dest="build">
+ <path refid="libs.path" />
+ </unjar>
+ </target>
+
+ <target name="dist" depends="clean, compile, unpackLibs">
+ <mkdir dir="dist" />
+ <tstamp>
+ <format property="distTime" pattern="yyyyMMdd'T'hhmmss" locale="en,US" />
+ </tstamp>
+ <jar basedir="build" destfile="dist/lombok-${distTime}.jar">
+ <manifest>
+ <attribute name="Premain-Class" value="lombok.agent.eclipse.EclipseParserPatcher" />
+ <attribute name="Can-Redefine-Classes" value="true" />
+ </manifest>
+ </jar>
+ <copy file="dist/lombok-${distTime}.jar" tofile="dist/lombok.jar" />
+ </target>
+</project>
diff --git a/contrib/asm-3.1-javadoc.zip b/contrib/asm-3.1-javadoc.zip
new file mode 100644
index 00000000..91ce1569
--- /dev/null
+++ b/contrib/asm-3.1-javadoc.zip
Binary files differ
diff --git a/contrib/asm-3.1-src.zip b/contrib/asm-3.1-src.zip
new file mode 100644
index 00000000..4ad32d54
--- /dev/null
+++ b/contrib/asm-3.1-src.zip
Binary files 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
--- /dev/null
+++ b/deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar
Binary files 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
--- /dev/null
+++ b/deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar
Binary files 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
--- /dev/null
+++ b/deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar
Binary files 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
--- /dev/null
+++ b/deps/org.eclipse.jdt.core_3.5.0.v_963.jar
Binary files 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
--- /dev/null
+++ b/lib/asm-3.1.jar
Binary files 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 <pfnguyen@hanhuy.com> 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'.
+ * <p>
+ *'name' may be used to set an alternative name for the property instead of
+ * defaulting to the field's name.
+ * <p>
+ *'readOnly' and 'writeOnly' may be set to true to create only a getter or a
+ * setter, respectively.
+ * <p>
+ *'useGet' is used to generate a getX instead of isX method if the property is
+ * a boolean.
+ * <p>
+ *'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
+ * <code>preCallMethod(this, String propertyName, oldValue, newValue)</code>;
+ * 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,
+ * <code>preCallMethod=anotherObject.validateProperty</code> would be ok, so
+ * long as <code>anotherObject</code> is a field within the bean. The object
+ * navigation leading up to the preCallMethod <b>cannot</b> contain any method
+ * invocations; e.g.
+ * <code>preCallMethod=someBean.someMethod().myPreCallMethod</code> would be
+ * illegal and cause an error.
+ * <p>
+ *'preCallThrows' specifies what exceptions can possibly be thrown by the
+ * preCallMethod. Any non-RuntimeExceptions <b>must</b> 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 <pfnguyen@hanhuy.com> 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<Void, StringBuilder> {
+ public final static String PROPERTY_ANNOTATION_FULL = "com.hanhuy.panno.Property";
+ public final static String PROPERTY_ANNOTATION = "Property";
+
+ private final Stack<String> currentClassNameStack = new Stack<String>();
+ private final Messager messager;
+ private final Map<String, Property> fieldMap;
+ private String currentClassName;
+ private String currentField;
+ private String currentType;
+
+ ASTTreeToSource(Map<String, Property> 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<com.sun.tools.javac.tree.JCTree> list, StringBuilder p) {
+ p.append("{\n");
+ boolean flag = true;
+
+ for ( com.sun.tools.javac.util.List<com.sun.tools.javac.tree.JCTree> 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<com.sun.tools.javac.tree.JCTree> 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 &lt;init&gt; 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 && "<init>".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 <pfnguyen@hanhuy.com> 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<? extends TypeElement> 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<? extends Element> 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<com.sun.tools.javac.tree.JCTree.JCStatement> 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<? extends AnnotationMirror> 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:
+ *
+ * <pre>public void run() {
+ * throw sneakyThrow(new IOException("You don't need to catch me!"));
+ * }</pre>
+ *
+ * NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care
+ * about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception
+ * from the java compiler.
+ *
+ * @param t The throwable to throw without requiring you to catch its type.
+ * @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws an exception!
+ */
+ public static RuntimeException sneakyThrow(Throwable t) {
+ if ( t == null ) throw new NullPointerException("t");
+ Lombok.<RuntimeException>sneakyThrow0(t);
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T {
+ throw (T)t;
+ }
+}
diff --git a/src/lombok/agent/eclipse/EclipseParserPatcher.java b/src/lombok/agent/eclipse/EclipseParserPatcher.java
new file mode 100644
index 00000000..abe26683
--- /dev/null
+++ b/src/lombok/agent/eclipse/EclipseParserPatcher.java
@@ -0,0 +1,47 @@
+package lombok.agent.eclipse;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.security.ProtectionDomain;
+
+public class EclipseParserPatcher {
+ private static class Patcher implements ClassFileTransformer {
+ @Override public byte[] transform(ClassLoader loader, String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer)
+ throws IllegalClassFormatException {
+
+ if ( !ECLIPSE_PARSER_CLASS_NAME.equals(className) ) return null;
+ EclipseParserTransformer transformer = new EclipseParserTransformer(classfileBuffer);
+ return transformer.transform();
+ }
+ }
+
+ static final String ECLIPSE_PARSER_CLASS_NAME = "org/eclipse/jdt/internal/compiler/parser/Parser";
+
+ public static void agentmain(String agentArgs, Instrumentation instrumentation) {
+ registerPatcher(instrumentation, true);
+ }
+
+ public static void premain(String agentArgs, Instrumentation instrumentation) {
+ registerPatcher(instrumentation, false);
+ }
+
+ private static void registerPatcher(Instrumentation instrumentation, boolean transformExisting) {
+ instrumentation.addTransformer(new Patcher(), true);
+
+ if ( transformExisting ) for ( Class<?> c : instrumentation.getAllLoadedClasses() ) {
+ if ( c.getName().equals(ECLIPSE_PARSER_CLASS_NAME) ) {
+ try {
+ instrumentation.retransformClasses(c);
+ } catch ( UnmodifiableClassException ex ) {
+ throw new UnsupportedOperationException(
+ "The eclipse parser class is already loaded and cannot be modified. " +
+ "You'll have to restart eclipse in order to use Lombok in eclipse.");
+ }
+ }
+ }
+ }
+}
diff --git a/src/lombok/agent/eclipse/EclipseParserTransformer.java b/src/lombok/agent/eclipse/EclipseParserTransformer.java
new file mode 100644
index 00000000..fa549ff8
--- /dev/null
+++ b/src/lombok/agent/eclipse/EclipseParserTransformer.java
@@ -0,0 +1,83 @@
+package lombok.agent.eclipse;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodAdapter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+class EclipseParserTransformer {
+ private static final String COMPILATION_UNIT_DECLARATION_SIG =
+ "Lorg/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration;";
+
+ private static final String TOPATCH_METHOD_NAME = "endParse";
+ private static final String TOPATCH_METHOD_DESC = "(I)" + COMPILATION_UNIT_DECLARATION_SIG;
+
+ private final byte[] in;
+
+ EclipseParserTransformer(byte[] classfileBuffer) {
+ in = classfileBuffer;
+ }
+
+ byte[] transform() {
+ ClassReader reader = new ClassReader(in);
+ ClassWriter writer = new ClassWriter(reader, 0);
+ ClassAdapter adapter = new ParserPatcherAdapter(writer);
+ reader.accept(adapter, 0);
+ return writer.toByteArray();
+ }
+
+ private static class ParserPatcherAdapter extends ClassAdapter {
+ public ParserPatcherAdapter(ClassVisitor cv) {
+ super(cv);
+ }
+
+ @Override public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor writerVisitor = super.visitMethod(access, name, desc, signature, exceptions);
+ if ( !TOPATCH_METHOD_NAME.equals(name) || !TOPATCH_METHOD_DESC.equals(desc) ) return writerVisitor;
+
+ return new PatcherMethodVisitor(writerVisitor);
+ }
+ }
+
+ static class PatcherMethodVisitor extends MethodAdapter {
+ private static final String TARGET_STATIC_CLASS = "java/lombok/ClassLoaderWorkaround";
+ private static final String TARGET_STATIC_METHOD_NAME = "transformCompilationUnitDeclaration";
+ private static final String TARGET_STATIC_METHOD_DESC = "(Ljava/lang/Object;)V";
+
+ private boolean alreadyCalled = false;
+
+ PatcherMethodVisitor(MethodVisitor mv) {
+ super(mv);
+ }
+
+ @Override public void visitInsn(int opcode) {
+ if ( opcode == Opcodes.ARETURN ) insertHookCall();
+
+ super.visitInsn(opcode);
+ }
+
+ @Override public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ if ( opcode == Opcodes.INVOKESTATIC &&
+ TARGET_STATIC_CLASS.equals(owner) && TARGET_STATIC_METHOD_NAME.equals(name) ) alreadyCalled = true;
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ /** When this method is called, the stack should hold the reference to the
+ * just-parsed CompilationUnitDeclaration object that is about to be returned
+ * to whomever wants it. We will put a call to a method of our choosing in,
+ * which will transform the CUD. The stack is not modified (that is, that method
+ * returns a CUD).
+ */
+ private void insertHookCall() {
+ if ( alreadyCalled ) return;
+ super.visitInsn(Opcodes.DUP);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET_STATIC_CLASS,
+ TARGET_STATIC_METHOD_NAME, TARGET_STATIC_METHOD_DESC);
+ }
+ }
+}
diff --git a/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java b/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java
new file mode 100644
index 00000000..0bcbc9ae
--- /dev/null
+++ b/src/lombok/agent/eclipse/TransformCompilationUnitDeclaration.java
@@ -0,0 +1,55 @@
+package lombok.agent.eclipse;
+
+import java.lang.reflect.Modifier;
+
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+
+public class TransformCompilationUnitDeclaration {
+ /** This is a 'magic' method signature - it is this one that will be called. Don't rename anything! */
+
+ public static void transform(CompilationUnitDeclaration ast) {
+ if ( ast.types != null ) for ( TypeDeclaration type : ast.types ) {
+ if ( type.fields != null ) for ( FieldDeclaration field : type.fields ) {
+ if ( field.annotations != null ) for ( Annotation annotation : field.annotations ) {
+ if ( annotation.type.toString().equals("Getter") ) addGetter(type);
+ }
+ }
+ }
+ }
+
+ private static void addGetter(TypeDeclaration type) {
+ for ( AbstractMethodDeclaration method : type.methods ) {
+ if ( method.selector != null && new String(method.selector).equals("getFoo") ) return;
+ }
+
+ MethodDeclaration method = new MethodDeclaration(type.compilationResult);
+ method.modifiers = Modifier.PUBLIC;
+ method.returnType = TypeReference.baseTypeReference(TypeReference.T_int, 0);
+ method.annotations = null;
+ method.arguments = null;
+ method.selector = "getFoo".toCharArray();
+ method.binding = null;
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.scope = new MethodScope(type.scope, method, false);
+ Expression fieldExpression = new SingleNameReference("foo".toCharArray(), 10);
+ Statement returnStatement = new ReturnStatement(fieldExpression, 1, 2);
+ method.statements = new Statement[] { returnStatement };
+ AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1];
+ System.arraycopy(type.methods, 0, newArray, 0, type.methods.length);
+ newArray[type.methods.length] = method;
+ type.methods = newArray;
+ System.out.println("Generated getFoo method");
+ }
+}
diff --git a/src/lombok/apt/AnnotationTransponder.java b/src/lombok/apt/AnnotationTransponder.java
new file mode 100644
index 00000000..01a79c12
--- /dev/null
+++ b/src/lombok/apt/AnnotationTransponder.java
@@ -0,0 +1,111 @@
+package lombok.apt;
+
+import static lombok.apt.PKG.CURRENT_SUPPORT;
+import static lombok.apt.PKG.isInstanceOf;
+import static lombok.apt.PKG.readResource;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+
+/**
+ * Responsible for redirecting the need to handle an annotation to a class that knows how to handle a given annotation type in a given compiler environment.
+ * Will dynamically locate a class in this package using the naming pattern: "HandleFoo_compilerType", e.g. "HandleGetter_ecj".
+ * Responsible for injecting the proper class into the right classloader so that it has open access to the classes required to inspect the live AST and
+ * modify it so that annotations can cause changes to the live AST.
+ *
+ * @author rzwitserloot
+ *
+ * @param <T> The annotation class that this transponder should handle (example: Getter.class).
+ */
+public class AnnotationTransponder<T extends Annotation> {
+ private HandlerForCompiler<T> impl;
+ private final ProcessingEnvironment processEnv;
+ private final RoundEnvironment roundEnv;
+ private String error;
+
+ @SuppressWarnings("unchecked")
+ private void createInstance(Class<T> annotation, ClassLoader loader, String compilerType) {
+ try {
+ if ( loader == null ) loader = AnnotationTransponder.class.getClassLoader();
+ Class<?> implClass;
+ try {
+ implClass = loader.loadClass(String.format(
+ "org.javanext.apt.Handle%s_%s", annotation.getSimpleName(), compilerType));
+ } catch ( ClassNotFoundException e ) {
+ implClass = loader.loadClass(String.format("lombok.apt.HandleANY_%s", compilerType));
+ }
+
+ Constructor<?> constructor;
+
+ constructor = implClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ impl = (HandlerForCompiler<T>)constructor.newInstance();
+ impl.processEnv = processEnv;
+ impl.roundEnv = roundEnv;
+ try {
+ impl.init();
+ } catch ( Exception e ) {
+ error = "Exception initializing handler: " + e;
+ impl = null;
+ }
+ } catch ( Exception e ) {
+ e.printStackTrace();
+ error = "You are using " + compilerType + " but a version that's changed the compiler internals. I can't work with it.";
+ }
+ }
+
+ public AnnotationTransponder(Class<T> annotation, RoundEnvironment roundEnv, ProcessingEnvironment processEnv) {
+ this.processEnv = processEnv;
+ this.roundEnv = roundEnv;
+ if ( isInstanceOf(processEnv, "com.sun.tools.javac.processing.JavacProcessingEnvironment") ) {
+ createInstance(annotation, null, "javac");
+ } else if ( isInstanceOf(processEnv, "org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl") ) {
+ final ClassLoader[] parentLoaders =
+ new ClassLoader[] { processEnv.getClass().getClassLoader(), AnnotationTransponder.class.getClassLoader() };
+
+ ClassLoader loader = new ClassLoader() {
+ @Override public Class<?> findClass(String name) throws ClassNotFoundException {
+ if ( name.equals(HandlerForCompiler.class.getName()) ) return HandlerForCompiler.class;
+ if ( name.startsWith(AnnotationTransponder.class.getPackage().getName()) ) {
+ byte[] data = readResource(name.replace(".", "/") + ".class");
+ return defineClass(name, data, 0, data.length);
+ }
+ for ( int i = 0 ; i < parentLoaders.length ; i++ ) {
+ try {
+ return parentLoaders[i].loadClass(name);
+ } catch ( ClassNotFoundException e ) {
+ if ( i == parentLoaders.length -1 ) throw e;
+ }
+ }
+
+ return null;
+ }
+ };
+
+ createInstance(annotation, loader, "ecj");
+ } else {
+ impl = null;
+ this.error = "I cannot work with your compiler. I currently only support " + CURRENT_SUPPORT + ".\n" +
+ "This is a: " + processEnv.getClass();
+ }
+ }
+
+
+ public void handle(Element element, T annotation) {
+ if ( impl == null ) {
+ processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, error, element);
+ } else {
+ try {
+ impl.handle(element, annotation);
+ } catch ( Exception e ) {
+ e.printStackTrace();
+ processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Exception in JavaNext: " + e, element);
+ }
+ }
+ }
+}
diff --git a/src/lombok/apt/HandleANY_ecj.java b/src/lombok/apt/HandleANY_ecj.java
new file mode 100644
index 00000000..e5a1792a
--- /dev/null
+++ b/src/lombok/apt/HandleANY_ecj.java
@@ -0,0 +1,18 @@
+package lombok.apt;
+
+import java.lang.annotation.Annotation;
+
+import javax.lang.model.element.Element;
+
+import javax.tools.Diagnostic;
+
+public class HandleANY_ecj extends HandlerForCompiler<Annotation> {
+ @Override public void handle(Element element, Annotation annotation) throws Exception {
+ //TODO: We should find eclipse's eclipse.ini file and patch us in as a javaagent and bootclasspath/a.
+ //Though, we should probably use reflection to find eclipse's SWT system and generate a popup dialog for
+ //confirmation.
+
+ String msg = "You'll need to install the eclipse patch. See http://lombok.github.org/ for more info.";
+ processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg, element);
+ }
+}
diff --git a/src/lombok/apt/HandleGetter_javac.java b/src/lombok/apt/HandleGetter_javac.java
new file mode 100644
index 00000000..8432e7b0
--- /dev/null
+++ b/src/lombok/apt/HandleGetter_javac.java
@@ -0,0 +1,75 @@
+package lombok.apt;
+
+import java.lang.reflect.Modifier;
+
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+
+import lombok.Getter;
+
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+
+class HandleGetter_javac extends HandlerForCompiler<Getter> {
+ private final Trees trees;
+ private final Messager messager;
+ private JavacProcessingEnvironment env;
+
+ HandleGetter_javac() {
+ this.messager = processEnv.getMessager();
+ this.trees = Trees.instance(processEnv);
+ }
+
+ @Override public void init() {
+ this.env = (JavacProcessingEnvironment)processEnv;
+ }
+
+ @Override public void handle(Element element, Getter getter) {
+ if ( !element.getKind().isField() ) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "@Getter is only supported on a field.");
+ return;
+ }
+
+ TypeElement classElement = (TypeElement)element.getEnclosingElement();
+ JCClassDecl javacClassTree = (JCClassDecl)trees.getTree(classElement);
+
+ Name.Table nameTable = Name.Table.instance(env.getContext());
+ TreeMaker treeMaker = TreeMaker.instance(env.getContext());
+
+ MethodTree getterMethod = createGetter(element, treeMaker, nameTable);
+ javacClassTree.defs = javacClassTree.defs.append((JCTree)getterMethod);
+
+ messager.printMessage(Diagnostic.Kind.WARNING, "Generated a getter for this field", element);
+ }
+
+ private MethodTree createGetter(Element field, TreeMaker treeMaker, Name.Table nameTable) {
+ JCStatement returnStatement = treeMaker.Return(treeMaker.Ident((Symbol)field));
+ JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement));
+ Name methodName = Name.fromString(nameTable, PKG.toGetterName(field));
+ JCExpression methodType = treeMaker.Type((Type)field.asType());
+
+ List<JCTypeParameter> methodGenericParams = List.nil();
+ List<JCVariableDecl> parameters = List.nil();
+ List<JCExpression> throwsClauses = List.nil();
+ JCExpression annotationMethodDefaultValue = null;
+
+ return treeMaker.MethodDef(treeMaker.Modifiers(Modifier.PUBLIC, List.<JCAnnotation>nil()), methodName, methodType,
+ methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue);
+ }
+}
diff --git a/src/lombok/apt/HandlerForCompiler.java b/src/lombok/apt/HandlerForCompiler.java
new file mode 100644
index 00000000..7eb29385
--- /dev/null
+++ b/src/lombok/apt/HandlerForCompiler.java
@@ -0,0 +1,16 @@
+package lombok.apt;
+
+import java.lang.annotation.Annotation;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+
+public abstract class HandlerForCompiler<T extends Annotation> {
+ protected ProcessingEnvironment processEnv;
+ protected RoundEnvironment roundEnv;
+
+ public void init() throws Exception {}
+
+ public abstract void handle(Element element, T annotation) throws Exception;
+}
diff --git a/src/lombok/apt/PKG.java b/src/lombok/apt/PKG.java
new file mode 100644
index 00000000..cfedf83b
--- /dev/null
+++ b/src/lombok/apt/PKG.java
@@ -0,0 +1,77 @@
+package lombok.apt;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+
+import lombok.Lombok;
+
+
+class PKG {
+ static final String CURRENT_SUPPORT = "javac 1.6 and eclipse (ecj).";
+
+ private PKG() {}
+
+ static boolean isInstanceOf(Object o, String className) {
+ if ( o == null ) return false;
+ return isInstanceOf(o.getClass(), className);
+ }
+
+ static boolean isInstanceOf(Class<?> c, String className) {
+ if ( c == Object.class || c == null ) return false;
+
+ if ( c.getName().equals(className) ) return true;
+
+ if ( isInstanceOf(c.getSuperclass(), className) ) return true;
+ for ( Class<?> iface : c.getInterfaces() ) {
+ if ( isInstanceOf(iface, className) ) return true;
+ }
+
+ return false;
+ }
+
+ static byte[] readResource(String name) {
+ return readResource(PKG.class.getClassLoader(), name);
+ }
+
+ static byte[] readResource(ClassLoader loader, String name) {
+ InputStream in = loader.getResourceAsStream(name);
+ if ( in == null ) throw Lombok.sneakyThrow(new IOException("Not found: " + name));
+
+ try {
+ return readStream(in);
+ } catch (IOException e) {
+ throw Lombok.sneakyThrow(e);
+ }
+ }
+
+ static String toGetterName(Element element) {
+ CharSequence fieldName = element.getSimpleName();
+ if ( fieldName.length() == 0 ) return "get";
+
+ final String prefix, suffix;
+
+ if ( element.asType().getKind() == TypeKind.BOOLEAN || "java.lang.Boolean".equals(element.asType().toString()) ) prefix = "is";
+ else prefix = "get";
+
+ char first = fieldName.charAt(0);
+ if ( Character.isLowerCase(first) )
+ suffix = String.format("%s%s", Character.toTitleCase(first), fieldName.subSequence(1, fieldName.length()));
+ else suffix = fieldName.toString();
+ return String.format("%s%s", prefix, suffix);
+ }
+
+ static byte[] readStream(InputStream in) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] b = new byte[65536];
+ while ( true ) {
+ int r = in.read(b);
+ if ( r == -1 ) break;
+ if ( r > 0 ) baos.write(b, 0, r);
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/src/lombok/apt/Processor.java b/src/lombok/apt/Processor.java
new file mode 100644
index 00000000..5cae88a0
--- /dev/null
+++ b/src/lombok/apt/Processor.java
@@ -0,0 +1,36 @@
+package lombok.apt;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+
+import lombok.Getter;
+
+
+@SupportedAnnotationTypes("lombok.*")
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+public class Processor extends AbstractProcessor {
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for ( TypeElement typeElement : annotations ) {
+ if ( typeElement.getQualifiedName().contentEquals(Getter.class.getName()) )
+ return handle(roundEnv, Getter.class, typeElement);
+ }
+
+ return false;
+ }
+
+ private <T extends Annotation> boolean handle(RoundEnvironment roundEnv, Class<T> annotation, TypeElement typeElement) {
+ for ( Element element : roundEnv.getElementsAnnotatedWith(typeElement) ) {
+ new AnnotationTransponder<T>(annotation, roundEnv, processingEnv).handle(element, element.getAnnotation(annotation));
+ }
+ return true;
+ }
+}