aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@tipit.to>2009-11-25 07:32:49 +0100
committerReinier Zwitserloot <reinier@tipit.to>2009-11-25 07:32:49 +0100
commit1a0e611a9c5e1ee518670647ce1a44beae559b44 (patch)
treee5ef8f671bc6688f486e874d4e2e1a7813e4f0b2 /src/core/lombok
parent7fd947ea40c25dad9ee543ebc4b92de9a2e05efc (diff)
downloadlombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.gz
lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.bz2
lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.zip
Refactored the source folders.
Diffstat (limited to 'src/core/lombok')
-rw-r--r--src/core/lombok/AccessLevel.java31
-rw-r--r--src/core/lombok/Cleanup.java82
-rw-r--r--src/core/lombok/Data.java60
-rw-r--r--src/core/lombok/EqualsAndHashCode.java80
-rw-r--r--src/core/lombok/Getter.java58
-rw-r--r--src/core/lombok/Lombok.java60
-rw-r--r--src/core/lombok/NonNull.java43
-rw-r--r--src/core/lombok/Setter.java55
-rw-r--r--src/core/lombok/SneakyThrows.java73
-rw-r--r--src/core/lombok/Synchronized.java46
-rw-r--r--src/core/lombok/ToString.java81
-rw-r--r--src/core/lombok/core/AST.java367
-rw-r--r--src/core/lombok/core/AnnotationValues.java419
-rw-r--r--src/core/lombok/core/LombokNode.java297
-rw-r--r--src/core/lombok/core/PrintAST.java51
-rw-r--r--src/core/lombok/core/SpiLoadUtil.java164
-rw-r--r--src/core/lombok/core/TransformationsUtil.java149
-rw-r--r--src/core/lombok/core/TypeLibrary.java79
-rw-r--r--src/core/lombok/core/TypeResolver.java114
-rw-r--r--src/core/lombok/core/Version.java48
-rw-r--r--src/core/lombok/core/package-info.java30
-rw-r--r--src/core/lombok/eclipse/Eclipse.java479
-rw-r--r--src/core/lombok/eclipse/EclipseAST.java366
-rw-r--r--src/core/lombok/eclipse/EclipseASTAdapter.java101
-rw-r--r--src/core/lombok/eclipse/EclipseASTVisitor.java295
-rw-r--r--src/core/lombok/eclipse/EclipseAnnotationHandler.java54
-rw-r--r--src/core/lombok/eclipse/EclipseNode.java175
-rw-r--r--src/core/lombok/eclipse/HandlerLibrary.java200
-rw-r--r--src/core/lombok/eclipse/TransformEclipseAST.java196
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java385
-rw-r--r--src/core/lombok/eclipse/handlers/HandleCleanup.java200
-rw-r--r--src/core/lombok/eclipse/handlers/HandleData.java243
-rw-r--r--src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java718
-rw-r--r--src/core/lombok/eclipse/handlers/HandleGetter.java154
-rw-r--r--src/core/lombok/eclipse/handlers/HandlePrintAST.java57
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSetter.java172
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSneakyThrows.java224
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSynchronized.java132
-rw-r--r--src/core/lombok/eclipse/handlers/HandleToString.java304
-rw-r--r--src/core/lombok/eclipse/handlers/package-info.java26
-rw-r--r--src/core/lombok/eclipse/package-info.java26
-rw-r--r--src/core/lombok/javac/HandlerLibrary.java219
-rw-r--r--src/core/lombok/javac/Javac.java162
-rw-r--r--src/core/lombok/javac/JavacAST.java347
-rw-r--r--src/core/lombok/javac/JavacASTAdapter.java98
-rw-r--r--src/core/lombok/javac/JavacASTVisitor.java266
-rw-r--r--src/core/lombok/javac/JavacAnnotationHandler.java58
-rw-r--r--src/core/lombok/javac/JavacNode.java212
-rw-r--r--src/core/lombok/javac/apt/Processor.java175
-rw-r--r--src/core/lombok/javac/apt/package-info.java26
-rw-r--r--src/core/lombok/javac/handlers/HandleCleanup.java147
-rw-r--r--src/core/lombok/javac/handlers/HandleData.java186
-rw-r--r--src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java447
-rw-r--r--src/core/lombok/javac/handlers/HandleGetter.java143
-rw-r--r--src/core/lombok/javac/handlers/HandlePrintAST.java57
-rw-r--r--src/core/lombok/javac/handlers/HandleSetter.java153
-rw-r--r--src/core/lombok/javac/handlers/HandleSneakyThrows.java110
-rw-r--r--src/core/lombok/javac/handlers/HandleSynchronized.java102
-rw-r--r--src/core/lombok/javac/handlers/HandleToString.java237
-rw-r--r--src/core/lombok/javac/handlers/JavacHandlerUtil.java335
-rw-r--r--src/core/lombok/javac/handlers/package-info.java26
-rw-r--r--src/core/lombok/javac/package-info.java26
-rw-r--r--src/core/lombok/package-info.java27
63 files changed, 10453 insertions, 0 deletions
diff --git a/src/core/lombok/AccessLevel.java b/src/core/lombok/AccessLevel.java
new file mode 100644
index 00000000..16edd108
--- /dev/null
+++ b/src/core/lombok/AccessLevel.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+/**
+ * Represents an AccessLevel. Used e.g. to specify the access level for generated methods and fields.
+ */
+public enum AccessLevel {
+ PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE,
+ /** Represents not generating anything or the complete lack of a method. */
+ NONE;
+}
diff --git a/src/core/lombok/Cleanup.java b/src/core/lombok/Cleanup.java
new file mode 100644
index 00000000..ce9e0aa9
--- /dev/null
+++ b/src/core/lombok/Cleanup.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Ensures the variable declaration that you annotate will be cleaned up by calling its close method, regardless
+ * of what happens. Implemented by wrapping all statements following the local variable declaration to the
+ * end of your scope into a try block that, as a finally action, closes the resource.
+ * <p>
+ * Example:
+ * <pre>
+ * public void copyFile(String in, String out) throws IOException {
+ * &#64;Cleanup FileInputStream inStream = new FileInputStream(in);
+ * &#64;Cleanup FileOutputStream outStream = new FileOutputStream(out);
+ * byte[] b = new byte[65536];
+ * while (true) {
+ * int r = inStream.read(b);
+ * if (r == -1) break;
+ * outStream.write(b, 0, r);
+ * }
+ * }
+ * </pre>
+ *
+ * Will generate:
+ * <pre>
+ * public void copyFile(String in, String out) throws IOException {
+ * &#64;Cleanup FileInputStream inStream = new FileInputStream(in);
+ * try {
+ * &#64;Cleanup FileOutputStream outStream = new FileOutputStream(out);
+ * try {
+ * byte[] b = new byte[65536];
+ * while (true) {
+ * int r = inStream.read(b);
+ * if (r == -1) break;
+ * outStream.write(b, 0, r);
+ * }
+ * } finally {
+ * out.close();
+ * }
+ * } finally {
+ * in.close();
+ * }
+ * }
+ * </pre>
+ *
+ * Note that the final close method call, if it throws an exception, will overwrite any exception thrown
+ * in the main body of the generated try block. You should NOT rely on this behaviour - future versions of
+ * lombok intend to silently swallow any exception thrown by the cleanup method <i>_IF</i> the main body
+ * throws an exception as well, as the earlier exception is usually far more useful.
+ * <p>
+ * However, in java 1.6, generating the code to do this is prohibitively complicated.
+ */
+@Target(ElementType.LOCAL_VARIABLE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Cleanup {
+ /** The name of the method that cleans up the resource. By default, 'close'. The method must not have any parameters. */
+ String value() default "close";
+}
diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java
new file mode 100644
index 00000000..488de640
--- /dev/null
+++ b/src/core/lombok/Data.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Generates getters for all fields, a useful toString method, and hashCode and equals implementations that check
+ * all non-transient fields. Will also generate setters for all non-final fields, as well as a constructor.
+ * <p>
+ * If any method to be generated already exists (in name - the return type or parameters are not relevant), then
+ * that method will not be generated by the Data annotation.
+ * <p>
+ * The generated constructor will have 1 parameter for each final field. The generated toString will print all fields,
+ * while the generated hashCode and equals take into account all non-transient fields.<br>
+ * Static fields are skipped (no getter or setter, and they are not included in toString, equals, hashCode, or the constructor).
+ * <p>
+ * {@code toString}, {@code equals}, and {@code hashCode} use the deepX variants in the
+ * {@code java.util.Arrays} utility class. Therefore, if your class has arrays that contain themselves,
+ * these methods will just loop endlessly until the inevitable {@code StackOverflowError}. This behaviour
+ * is no different from {@code java.util.ArrayList}, though.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Data {
+ /**
+ * If you specify a static constructor name, then the generated constructor will be private, and
+ * instead a static factory method is created that other classes can use to create instances.
+ * We suggest the name: "of", like so:
+ *
+ * <pre>
+ * public @Data(staticConstructor = "of") class Point { final int x, y; }
+ * </pre>
+ *
+ * Default: No static constructor, instead the normal constructor is public.
+ */
+ String staticConstructor() default "";
+}
diff --git a/src/core/lombok/EqualsAndHashCode.java b/src/core/lombok/EqualsAndHashCode.java
new file mode 100644
index 00000000..88d72051
--- /dev/null
+++ b/src/core/lombok/EqualsAndHashCode.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Generates implementations for the {@code equals} and {@code hashCode} methods inherited by all objects.
+ * <p>
+ * If either method already exists, then {@code @EqualsAndHashCode} will not generate that particular method.
+ * If they all exist, {@code @EqualsAndHashCode} generates no methods, and emits a warning instead to highlight
+ * that its doing nothing at all. The parameter list and return type are not relevant when deciding to skip generation of
+ * a method; any method named {@code hashCode} will make {@code @EqualsAndHashCode} not generate that method,
+ * for example.
+ * <p>
+ * By default, all fields that are non-static and non-transient are used in the equality check and hashCode generation.
+ * You can exclude more fields by specifying them in the {@code exclude} parameter. You can also explicitly specify
+ * the fields that are to be used by specifying them in the {@code of} parameter.
+ * <p>
+ * Normally, auto-generating {@code hashCode} and {@code equals} implementations in a subclass is a bad idea, as
+ * the superclass also defines fields, for which equality checks/hashcodes won't be auto-generated. Therefore, a warning
+ * is emitted when you try. Instead, you can set the {@code callSuper} parameter to <em>true</em> which will call
+ * {@code super.equals} and {@code super.hashCode}. Doing this with {@code java.lang.Object} as superclass is
+ * pointless, so, conversely, setting this flag when <em>NOT</em> extending something (other than Object) will also generate
+ * a warning. Be aware that not all implementations of {@code equals} correctly handle being called from a subclass!
+ * Fortunately, lombok-generated {@code equals} implementations do correctly handle it.
+ * <p>
+ * Array fields are handled by way of {@link java.util.Arrays#deepEquals(Object[], Object[])} where necessary, as well
+ * as {@code deepHashCode}. The downside is that arrays with circular references (arrays that contain themselves,
+ * possibly indirectly) results in calls to {@code hashCode} and {@code equals} throwing a
+ * {@link java.lang.StackOverflowError}. However, the implementations for java's own {@link java.util.ArrayList} suffer
+ * from the same flaw.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface EqualsAndHashCode {
+ /**
+ * Any fields listed here will not be taken into account in the generated
+ * {@code equals} and {@code hashCode} implementations.
+ * Mutually exclusive with {@link #of()}.
+ */
+ String[] exclude() default {};
+
+ /**
+ * If present, explicitly lists the fields that are to be used for identity.
+ * Normally, all non-static, non-transient fields are used for identity.
+ * <p>
+ * Mutually exclusive with {@link #exclude()}.
+ */
+ String[] of() default {};
+
+ /**
+ * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating
+ * for the fields in this class.
+ * <strong>default: false</strong>
+ */
+ boolean callSuper() default false;
+}
diff --git a/src/core/lombok/Getter.java b/src/core/lombok/Getter.java
new file mode 100644
index 00000000..fa84954c
--- /dev/null
+++ b/src/core/lombok/Getter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Put on any field to make lombok build a standard getter.
+ *
+ * Example:
+ * <pre>
+ * private &#64;Getter int foo;
+ * </pre>
+ *
+ * will generate:
+ *
+ * <pre>
+ * public int getFoo() {
+ * return this.foo;
+ * }
+ * </pre>
+ *
+ * Note that fields of type {@code boolean} (but not {@code java.lang.Boolean}) will result in an
+ * {@code isFoo} name instead of {@code getFoo}.
+ * <p>
+ * If any method named {@code getFoo}/{@code isFoo} exists, regardless of return type or parameters, no method is generated,
+ * and instead a compiler warning is emitted.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Getter {
+ /**
+ * If you want your setter to be non-public, you can specify an alternate access level here.
+ */
+ lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
+}
diff --git a/src/core/lombok/Lombok.java b/src/core/lombok/Lombok.java
new file mode 100644
index 00000000..71684f4f
--- /dev/null
+++ b/src/core/lombok/Lombok.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+/**
+ * Useful utility methods to manipulate lombok-generated code.
+ */
+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.
+ * <p>
+ * Example usage:
+ * <p>
+ * <pre>public void run() {
+ * throw sneakyThrow(new IOException("You don't need to catch me!"));
+ * }</pre>
+ * <p>
+ * 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.
+ * <p>
+ * Note that this method has a return type of {@code RuntimeException} it is advised you always call this
+ * method as argument to the {@code throw} statement to avoid compiler errors regarding no return
+ * statement and similar problems. This method won't of course return an actual {@code RuntimeException} -
+ * it never returns, it always throws the provided exception.
+ *
+ * @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/core/lombok/NonNull.java b/src/core/lombok/NonNull.java
new file mode 100644
index 00000000..08eec2a5
--- /dev/null
+++ b/src/core/lombok/NonNull.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Lombok is smart enough to translate any annotation named {@code @NonNull} or {@code @NotNull} in any casing and
+ * with any package name to the return type of generated getters and the parameter of generated setters and constructors,
+ * as well as generate the appropriate null checks in the setter and constructor.
+ *
+ * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just
+ * about anyone elses. As long as it is named {@code @NonNull} or {@code @NotNull}.
+ *
+ * WARNING: If the java community ever does decide on supporting a single {@code @NonNull} annotation (for example via JSR305), then
+ * this annotation will <strong>be deleted</strong> from the lombok package. If the need to update an import statement scares
+ * you, you should use your own annotation named {@code @NonNull} instead of this one.
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface NonNull {}
diff --git a/src/core/lombok/Setter.java b/src/core/lombok/Setter.java
new file mode 100644
index 00000000..778bb00d
--- /dev/null
+++ b/src/core/lombok/Setter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Put on any field to make lombok build a standard setter.
+ * <p>
+ * Example:
+ * <pre>
+ * private &#64;Setter int foo;
+ * </pre>
+ *
+ * will generate:
+ *
+ * <pre>
+ * public void setFoo(int foo) {
+ * this.foo = foo;
+ * }
+ * </pre>
+ *
+ * If any method named {@code setFoo} exists, regardless of return type or parameters, no method is generated,
+ * and instead a compiler warning is emitted.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Setter {
+ /**
+ * If you want your setter to be non-public, you can specify an alternate access level here.
+ */
+ lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
+}
diff --git a/src/core/lombok/SneakyThrows.java b/src/core/lombok/SneakyThrows.java
new file mode 100644
index 00000000..1feeadf1
--- /dev/null
+++ b/src/core/lombok/SneakyThrows.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * &#64;SneakyThrow will avoid javac's insistence that you either catch or throw onward any checked exceptions that
+ * statements in your method body declare they generate.
+ * <p>
+ * &#64;SneakyThrow does not silently swallow, wrap into RuntimeException, or otherwise modify any exceptions of the listed
+ * checked exception types. The JVM does not check for the consistency of the checked exception system; javac does,
+ * and this annotation lets you opt out of its mechanism.
+ * <p>
+ * You should use this annotation ONLY in the following two cases:<ol>
+ * <li>You are certain the listed exception can't actually ever happen, or only in vanishingly rare situations.
+ * You don't try to catch OutOfMemoryError on every statement either. Examples:<br>
+ * {@code IOException} in {@code ByteArrayOutputStream}<br>
+ * {@code UnsupportedEncodingException} in new String(byteArray, "UTF-8").</li>
+ * <li>You know for certain the caller can handle the exception (for example, because the caller is
+ * an app manager that will handle all throwables that fall out of your method the same way), but due
+ * to interface restrictions you can't just add these exceptions to your 'throws' clause.
+ * <p>
+ * Note that, as SneakyThrow is an implementation detail and <i>NOT</i> part of your method signature, it is
+ * a compile time error if none of the statements in your method body can throw a listed exception.
+ * <p>
+ * <b><i>WARNING: </b></i>You must have lombok.jar available at the runtime of your app if you use SneakyThrows,
+ * because your code is rewritten to use {@link Lombok#sneakyThrow(Throwable)}.
+ * <p>
+ * <p>
+ * Example:
+ * <pre>
+ * &#64;SneakyThrows(UnsupportedEncodingException.class)
+ * public void utf8ToString(byte[] bytes) {
+ * return new String(bytes, "UTF-8");
+ * }
+ * </pre>
+ *
+ * {@code @SneakyThrows} without a parameter defaults to allowing <i>every</i> checked exception.
+ * (The default is {@code Throwable.class}).
+ *
+ * @see Lombok#sneakyThrow(Throwable)
+ */
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.SOURCE)
+public @interface SneakyThrows {
+ /** The exception type(s) you want to sneakily throw onward. */
+ Class<? extends Throwable>[] value() default java.lang.Throwable.class;
+
+ //The package is mentioned in java.lang due to a bug in javac (presence of an annotation processor throws off the type resolver for some reason).
+}
diff --git a/src/core/lombok/Synchronized.java b/src/core/lombok/Synchronized.java
new file mode 100644
index 00000000..72c44c71
--- /dev/null
+++ b/src/core/lombok/Synchronized.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Almost exactly like putting the 'synchronized' keyword on a method, except will synchronize on a private internal
+ * Object, so that other code not under your control doesn't meddle with your thread management by locking on
+ * your own instance.
+ * <p>
+ * For non-static methods, a field named {@code $lock} is used, and for static methods,
+ * {@code $LOCK} is used. These will be generated if needed and if they aren't already present. The contents
+ * of the fields will be serializable.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Synchronized {
+ /**
+ * Optional: specify the name of a different field to lock on. It is a compile time error if this field
+ * doesn't already exist (the fields are automatically generated only if you don't specify a specific name.
+ */
+ String value() default "";
+}
diff --git a/src/core/lombok/ToString.java b/src/core/lombok/ToString.java
new file mode 100644
index 00000000..7b89d481
--- /dev/null
+++ b/src/core/lombok/ToString.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Generates an implementation for the {@code toString} method inherited by all objects.
+ * <p>
+ * If the method already exists, then {@code ToString} will not generate any method, and instead warns
+ * that it's doing nothing at all. The parameter list and return type are not relevant when deciding to skip generation of
+ * the method; any method named {@code toString} will make {@code ToString} not generate anything.
+ * <p>
+ * By default, all fields that are non-static are used in the toString generation. You can exclude fields by specifying them
+ * in the {@code exclude} parameter. You can also explicitly specify the fields that
+ * are to be used by specifying them in the {@code of} parameter.
+ * <p>
+ * Array fields are handled by way of {@link java.util.Arrays#deepToString(Object[])} where necessary.
+ * The downside is that arrays with circular references (arrays that contain themselves,
+ * possibly indirectly) results in calls to {@code toString} throwing a
+ * {@link java.lang.StackOverflowError}. However, the implementations for java's own {@link java.util.ArrayList} suffer
+ * from the same flaw.
+ * <p>
+ * The {@code toString} method that is generated will print the class name as well as each field (both the name
+ * and the value). You can optionally choose to suppress the printing of the field name, by setting the
+ * {@code includeFieldNames} flag to <em>false</em>.
+ * <p>
+ * You can also choose to include the result of {@code toString} in your class's superclass by setting the
+ * {@code callSuper} to <em>true</em>.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface ToString {
+ /**
+ * Include the name of each field when printing it.
+ * <strong>default: true</strong>
+ */
+ boolean includeFieldNames() default true;
+
+ /**
+ * Any fields listed here will not be printed in the generated {@code toString} implementation.
+ * Mutually exclusive with {@link #of()}.
+ */
+ String[] exclude() default {};
+
+ /**
+ * If present, explicitly lists the fields that are to be printed.
+ * Normally, all non-static fields are printed.
+ * <p>
+ * Mutually exclusive with {@link #exclude()}.
+ */
+ String[] of() default {};
+
+ /**
+ * Include the result of the superclass's implementation of {@code toString} in the output.
+ * <strong>default: false</strong>
+ */
+ boolean callSuper() default false;
+}
diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java
new file mode 100644
index 00000000..6d786d1e
--- /dev/null
+++ b/src/core/lombok/core/AST.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import static lombok.Lombok.sneakyThrow;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Lombok wraps the AST produced by a target platform into its own AST system, mostly because both Eclipse and javac
+ * do not allow upward traversal (from a method to its owning type, for example).
+ *
+ * @param A Self-type.
+ * @param L type of all LombokNodes.
+ * @param N The common type of all AST nodes in the internal representation of the target platform.
+ * For example, JCTree for javac, and ASTNode for Eclipse.
+ */
+public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> {
+ /** The kind of node represented by a given AST.Node object. */
+ public enum Kind {
+ COMPILATION_UNIT, TYPE, FIELD, INITIALIZER, METHOD, ANNOTATION, ARGUMENT, LOCAL, STATEMENT;
+ }
+
+ private L top;
+ private final String fileName;
+ Map<N, Void> identityDetector = new IdentityHashMap<N, Void>();
+ private Map<N, L> nodeMap = new IdentityHashMap<N, L>();
+
+ protected AST(String fileName) {
+ this.fileName = fileName == null ? "(unknown).java" : fileName;
+ }
+
+ /** Set the node object that wraps the internal Compilation Unit node. */
+ protected void setTop(L top) {
+ this.top = top;
+ }
+
+ /**
+ * Return the content of the package declaration on this AST's top (Compilation Unit) node.
+ *
+ * Example: "java.util".
+ */
+ public abstract String getPackageDeclaration();
+
+ /**
+ * Return the contents of each non-static import statement on this AST's top (Compilation Unit) node.
+ *
+ * Example: "java.util.IOException".
+ */
+ public abstract Collection<String> getImportStatements();
+
+ /**
+ * Puts the given node in the map so that javac/Eclipse's own internal AST object can be translated to
+ * an AST.Node object. Also registers the object as visited to avoid endless loops.
+ */
+ protected L putInMap(L node) {
+ nodeMap.put(node.get(), node);
+ identityDetector.put(node.get(), null);
+ return node;
+ }
+
+ /** Returns the node map, that can map javac/Eclipse internal AST objects to AST.Node objects. */
+ protected Map<N, L> getNodeMap() {
+ return nodeMap;
+ }
+
+ /** Clears the registry that avoids endless loops, and empties the node map. The existing node map
+ * object is left untouched, and instead a new map is created. */
+ protected void clearState() {
+ identityDetector = new IdentityHashMap<N, Void>();
+ nodeMap = new IdentityHashMap<N, L>();
+ }
+
+ /**
+ * Marks the stated node as handled (to avoid endless loops if 2 nodes refer to each other, or a node
+ * refers to itself). Will then return true if it was already set as handled before this call - in which
+ * case you should do nothing lest the AST build process loops endlessly.
+ */
+ protected boolean setAndGetAsHandled(N node) {
+ if (identityDetector.containsKey(node)) return true;
+ identityDetector.put(node, null);
+ return false;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ /** The AST.Node object representing the Compilation Unit. */
+ public L top() {
+ return top;
+ }
+
+ /** Maps a javac/Eclipse internal AST Node to the appropriate AST.Node object. */
+ public L get(N node) {
+ return nodeMap.get(node);
+ }
+
+ @SuppressWarnings("unchecked")
+ L replaceNewWithExistingOld(Map<N, L> oldNodes, L newNode) {
+ L oldNode = oldNodes.get(newNode.get());
+ L targetNode = oldNode == null ? newNode : oldNode;
+
+ List children = new ArrayList();
+ for (L child : newNode.children) {
+ L oldChild = replaceNewWithExistingOld(oldNodes, child);
+ children.add(oldChild);
+ oldChild.parent = targetNode;
+ }
+
+ targetNode.children.clear();
+ ((List)targetNode.children).addAll(children);
+ return targetNode;
+ }
+
+ /** Build an AST.Node object for the stated internal (javac/Eclipse) AST Node object. */
+ protected abstract L buildTree(N item, Kind kind);
+
+ /**
+ * Represents a field that contains AST children.
+ */
+ protected static class FieldAccess {
+ /** The actual field. */
+ public final Field field;
+ /** Dimensions of the field. Works for arrays, or for java.util.collections. */
+ public final int dim;
+
+ FieldAccess(Field field, int dim) {
+ this.field = field;
+ this.dim = dim;
+ }
+ }
+
+ private static Map<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new HashMap<Class<?>, Collection<FieldAccess>>();
+
+ /** Returns FieldAccess objects for the stated class. Each field that contains objects of the kind returned by
+ * {@link #getStatementTypes()}, either directly or inside of an array or java.util.collection (or array-of-arrays,
+ * or collection-of-collections, etcetera), is returned.
+ */
+ protected Collection<FieldAccess> fieldsOf(Class<?> c) {
+ Collection<FieldAccess> fields = fieldsOfASTClasses.get(c);
+ if (fields != null) return fields;
+
+ fields = new ArrayList<FieldAccess>();
+ getFields(c, fields);
+ fieldsOfASTClasses.put(c, fields);
+ return fields;
+ }
+
+ private void getFields(Class<?> c, Collection<FieldAccess> fields) {
+ if (c == Object.class || c == null) return;
+ for (Field f : c.getDeclaredFields()) {
+ if (Modifier.isStatic(f.getModifiers())) continue;
+ Class<?> t = f.getType();
+ int dim = 0;
+
+ if (t.isArray()) {
+ while (t.isArray()) {
+ dim++;
+ t = t.getComponentType();
+ }
+ } else {
+ while (Collection.class.isAssignableFrom(t)) {
+ dim++;
+ t = getComponentType(f.getGenericType());
+ }
+ }
+
+ for (Class<?> statementType : getStatementTypes()) {
+ if (statementType.isAssignableFrom(t)) {
+ f.setAccessible(true);
+ fields.add(new FieldAccess(f, dim));
+ break;
+ }
+ }
+ }
+ getFields(c.getSuperclass(), fields);
+ }
+
+ private Class<?> getComponentType(Type type) {
+ if (type instanceof ParameterizedType) {
+ Type component = ((ParameterizedType)type).getActualTypeArguments()[0];
+ return component instanceof Class<?> ? (Class<?>)component : Object.class;
+ }
+ return Object.class;
+ }
+
+ /**
+ * The supertypes which are considered AST Node children. Usually, the Statement, and the Expression,
+ * though some platforms (such as Eclipse) group these under one common supertype. */
+ protected abstract Collection<Class<? extends N>> getStatementTypes();
+
+ /**
+ * buildTree implementation that uses reflection to find all child nodes by way of inspecting
+ * the fields. */
+ protected Collection<L> buildWithField(Class<L> nodeType, N statement, FieldAccess fa) {
+ List<L> list = new ArrayList<L>();
+ buildWithField0(nodeType, statement, fa, list);
+ return list;
+ }
+
+ /**
+ * Uses reflection to find the given direct child on the given statement, and replace it with a new child.
+ */
+ protected boolean replaceStatementInNode(N statement, N oldN, N newN) {
+ for (FieldAccess fa : fieldsOf(statement.getClass())) {
+ if (replaceStatementInField(fa, statement, oldN, newN)) return true;
+ }
+
+ return false;
+ }
+
+ private boolean replaceStatementInField(FieldAccess fa, N statement, N oldN, N newN) {
+ try {
+ Object o = fa.field.get(statement);
+ if (o == null) return false;
+
+ if (o == oldN) {
+ fa.field.set(statement, newN);
+ return true;
+ }
+
+ if (fa.dim > 0) {
+ if (o.getClass().isArray()) {
+ return replaceStatementInArray(o, oldN, newN);
+ } else if (Collection.class.isInstance(o)) {
+ return replaceStatementInCollection(fa.field, statement, new ArrayList<Collection<?>>(), (Collection<?>)o, oldN, newN);
+ }
+ }
+
+ return false;
+ } catch (IllegalAccessException e) {
+ throw sneakyThrow(e);
+ }
+
+ }
+
+ private boolean replaceStatementInCollection(Field field, Object fieldRef, List<Collection<?>> chain, Collection<?> collection, N oldN, N newN) throws IllegalAccessException {
+ if (collection == null) return false;
+
+ int idx = -1;
+ for (Object o : collection) {
+ idx++;
+ if (o == null) continue;
+ if (Collection.class.isInstance(o)) {
+ Collection<?> newC = (Collection<?>)o;
+ List<Collection<?>> newChain = new ArrayList<Collection<?>>(chain);
+ newChain.add(newC);
+ if (replaceStatementInCollection(field, fieldRef, newChain, newC, oldN, newN)) return true;
+ }
+ if (o == oldN) {
+ setElementInASTCollection(field, fieldRef, chain, collection, idx, newN);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Override if your AST collection does not support the set method. Javac's for example, does not.
+ *
+ * @param field The field that contains the array or list of AST nodes.
+ * @param fieldRef The object that you can supply to the field's {@code get} method.
+ * @param chain If the collection is immutable, you need to update the pointer to the collection in each element in the chain.
+ *
+ * @throws IllegalAccessException This exception won't happen, but we allow you to throw it so you can avoid having to catch it.
+ */
+ @SuppressWarnings("unchecked")
+ protected void setElementInASTCollection(Field field, Object fieldRef, List<Collection<?>> chain, Collection<?> collection, int idx, N newN) throws IllegalAccessException {
+ if (collection instanceof List<?>) {
+ ((List)collection).set(idx, newN);
+ }
+ }
+
+ private boolean replaceStatementInArray(Object array, N oldN, N newN) {
+ if (array == null) return false;
+
+ int len = Array.getLength(array);
+ for (int i = 0; i < len; i++) {
+ Object o = Array.get(array, i);
+ if (o == null) continue;
+ if (o.getClass().isArray()) {
+ if (replaceStatementInArray(o, oldN, newN)) return true;
+ } else if (o == oldN) {
+ Array.set(array, i, newN);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void buildWithField0(Class<L> nodeType, N child, FieldAccess fa, Collection<L> list) {
+ try {
+ Object o = fa.field.get(child);
+ if (o == null) return;
+ if (fa.dim == 0) {
+ L node = buildTree((N)o, Kind.STATEMENT);
+ if (node != null) list.add(nodeType.cast(node));
+ } else if (o.getClass().isArray()) {
+ buildWithArray(nodeType, o, list, fa.dim);
+ } else if (Collection.class.isInstance(o)) {
+ buildWithCollection(nodeType, o, list, fa.dim);
+ }
+ } catch (IllegalAccessException e) {
+ sneakyThrow(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void buildWithArray(Class<L> nodeType, Object array, Collection<L> list, int dim) {
+ if (dim == 1) {
+ for (Object v : (Object[])array) {
+ if (v == null) continue;
+ L node = buildTree((N)v, Kind.STATEMENT);
+ if (node != null) list.add(nodeType.cast(node));
+ }
+ } else for (Object v : (Object[])array) {
+ if (v == null) return;
+ buildWithArray(nodeType, v, list, dim -1);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void buildWithCollection(Class<L> nodeType, Object collection, Collection<L> list, int dim) {
+ if (dim == 1) {
+ for (Object v : (Collection<?>)collection) {
+ if (v == null) continue;
+ L node = buildTree((N)v, Kind.STATEMENT);
+ if (node != null) list.add(nodeType.cast(node));
+ }
+ } else for (Object v : (Collection<?>)collection) {
+ buildWithCollection(nodeType, v, list, dim-1);
+ }
+ }
+}
diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java
new file mode 100644
index 00000000..0408de85
--- /dev/null
+++ b/src/core/lombok/core/AnnotationValues.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a single annotation in a source file and can be used to query the parameters present on it.
+ *
+ * @param A The annotation that this class represents, such as {@code lombok.Getter}
+ */
+public class AnnotationValues<A extends Annotation> {
+ private final Class<A> type;
+ private final Map<String, AnnotationValue> values;
+ private final LombokNode<?, ?, ?> ast;
+
+ /**
+ * Represents a single method on the annotation class. For example, the value() method on the Getter annotation.
+ */
+ public static class AnnotationValue {
+ /** A list of the raw expressions. List is size 1 unless an array is provided. */
+ public final List<String> raws;
+
+ /** Guesses for each raw expression. If the raw expression is a literal expression, the guess will
+ * likely be right. If not, it'll be wrong. */
+ public final List<Object> valueGuesses;
+ private final LombokNode<?, ?, ?> node;
+ private final boolean isExplicit;
+
+ /**
+ * 'raw' should be the exact expression, for example '5+7', 'AccessLevel.PUBLIC', or 'int.class'.
+ * 'valueGuess' should be a likely guess at the real value intended.
+ *
+ * For classes, supply the class name (qualified or not) as a string.<br />
+ * For enums, supply the simple name part (everything after the last dot) as a string.<br />
+ */
+ public AnnotationValue(LombokNode<?, ?, ?> node, String raw, Object valueGuess, boolean isExplicit) {
+ this.node = node;
+ this.raws = Collections.singletonList(raw);
+ this.valueGuesses = Collections.singletonList(valueGuess);
+ this.isExplicit = isExplicit;
+ }
+
+ /**
+ * Like the other constructor, but used for when the annotation method is initialized with an array value.
+ */
+ public AnnotationValue(LombokNode<?, ?, ?> node, List<String> raws, List<Object> valueGuesses, boolean isExplicit) {
+ this.node = node;
+ this.raws = raws;
+ this.valueGuesses = valueGuesses;
+ this.isExplicit = isExplicit;
+ }
+
+ /**
+ * Override this if you want more specific behaviour (to get the source position just right).
+ *
+ * @param message English message with the problem.
+ * @param valueIdx The index into the values for this annotation key that caused the problem.
+ * -1 for a problem that applies to all values, otherwise the 0-based index into an array of values.
+ * If there is no array for this value (e.g. value=1 instead of value={1,2}), then always -1 or 0.
+ */
+ public void setError(String message, int valueIdx) {
+ node.addError(message);
+ }
+
+ /**
+ * Override this if you want more specific behaviour (to get the source position just right).
+ *
+ * @param message English message with the problem.
+ * @param valueIdx The index into the values for this annotation key that caused the problem.
+ * -1 for a problem that applies to all values, otherwise the 0-based index into an array of values.
+ * If there is no array for this value (e.g. value=1 instead of value={1,2}), then always -1 or 0.
+ */
+ public void setWarning(String message, int valueIdx) {
+ node.addError(message);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "raws: " + raws + " valueGuesses: " + valueGuesses;
+ }
+
+ public boolean isExplicit() {
+ return isExplicit;
+ }
+ }
+
+ /**
+ * Creates a new AnnotationValues.
+ *
+ * @param type The annotation type. For example, "Getter.class"
+ * @param values a Map of method names to AnnotationValue instances, for example 'value -> annotationValue instance'.
+ * @param ast The Annotation node.
+ */
+ public AnnotationValues(Class<A> type, Map<String, AnnotationValue> values, LombokNode<?, ?, ?> ast) {
+ this.type = type;
+ this.values = values;
+ this.ast = ast;
+ }
+
+ /**
+ * Thrown on the fly if an actual annotation instance procured via the {@link #getInstance()} method is queried
+ * for a method for which this AnnotationValues instance either doesn't have a guess or can't manage to fit
+ * the guess into the required data type.
+ */
+ public static class AnnotationValueDecodeFail extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /** The index into an array initializer (e.g. if the second value in an array initializer is
+ * an integer constant expression like '5+SomeOtherClass.CONSTANT', this exception will be thrown,
+ * and you'll get a '1' for idx. */
+ public final int idx;
+
+ /** The AnnotationValue object that goes with the annotation method for which the failure occurred. */
+ public final AnnotationValue owner;
+
+ public AnnotationValueDecodeFail(AnnotationValue owner, String msg, int idx) {
+ super(msg);
+ this.idx = idx;
+ this.owner = owner;
+ }
+ }
+
+ private static AnnotationValueDecodeFail makeNoDefaultFail(AnnotationValue owner, Method method) {
+ return new AnnotationValueDecodeFail(owner,
+ "No value supplied but " + method.getName() + " has no default either.", -1);
+ }
+
+ private A cachedInstance = null;
+
+ /**
+ * Creates an actual annotation instance. You can use this to query any annotation methods, except for
+ * those annotation methods with class literals, as those can most likely not be turned into Class objects.
+ *
+ * If some of the methods cannot be implemented, this method still works; it's only when you call a method
+ * that has a problematic value that an AnnotationValueDecodeFail exception occurs.
+ */
+ @SuppressWarnings("unchecked")
+ public A getInstance() {
+ if (cachedInstance != null) return cachedInstance;
+ InvocationHandler invocations = new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ AnnotationValue v = values.get(method.getName());
+ if (v == null) {
+ Object defaultValue = method.getDefaultValue();
+ if (defaultValue != null) return defaultValue;
+ throw makeNoDefaultFail(v, method);
+ }
+
+ boolean isArray = false;
+ Class<?> expected = method.getReturnType();
+ Object array = null;
+ if (expected.isArray()) {
+ isArray = true;
+ expected = expected.getComponentType();
+ array = Array.newInstance(expected, v.valueGuesses.size());
+ }
+
+ if (!isArray && v.valueGuesses.size() > 1) {
+ throw new AnnotationValueDecodeFail(v,
+ "Expected a single value, but " + method.getName() + " has an array of values", -1);
+ }
+
+ if (v.valueGuesses.size() == 0 && !isArray) {
+ Object defaultValue = method.getDefaultValue();
+ if (defaultValue == null) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ }
+
+ int idx = 0;
+ for (Object guess : v.valueGuesses) {
+ Object result = guess == null ? null : guessToType(guess, expected, v, idx);
+ if (!isArray) {
+ if (result == null) {
+ Object defaultValue = method.getDefaultValue();
+ if (defaultValue == null) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ }
+ return result;
+ }
+ if (result == null) {
+ if (v.valueGuesses.size() == 1) {
+ Object defaultValue = method.getDefaultValue();
+ if (defaultValue == null) throw makeNoDefaultFail(v, method);
+ return defaultValue;
+ }
+ throw new AnnotationValueDecodeFail(v,
+ "I can't make sense of this annotation value. Try using a fully qualified literal.", idx);
+ }
+ Array.set(array, idx++, result);
+ }
+
+ return array;
+ }
+ };
+
+ return cachedInstance = (A) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, invocations);
+ }
+
+ private Object guessToType(Object guess, Class<?> expected, AnnotationValue v, int pos) {
+ if (expected == int.class) {
+ if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) {
+ return ((Number)guess).intValue();
+ }
+ }
+
+ if (expected == long.class) {
+ if (guess instanceof Long || guess instanceof Integer || guess instanceof Short || guess instanceof Byte) {
+ return ((Number)guess).longValue();
+ }
+ }
+
+ if (expected == short.class) {
+ if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) {
+ int intVal = ((Number)guess).intValue();
+ int shortVal = ((Number)guess).shortValue();
+ if (shortVal == intVal) return shortVal;
+ }
+ }
+
+ if (expected == byte.class) {
+ if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) {
+ int intVal = ((Number)guess).intValue();
+ int byteVal = ((Number)guess).byteValue();
+ if (byteVal == intVal) return byteVal;
+ }
+ }
+
+ if (expected == double.class) {
+ if (guess instanceof Number) return ((Number)guess).doubleValue();
+ }
+
+ if (expected == float.class) {
+ if (guess instanceof Number) return ((Number)guess).floatValue();
+ }
+
+ if (expected == boolean.class) {
+ if (guess instanceof Boolean) return ((Boolean)guess).booleanValue();
+ }
+
+ if (expected == char.class) {
+ if (guess instanceof Character) return ((Character)guess).charValue();
+ }
+
+ if (expected == String.class) {
+ if (guess instanceof String) return guess;
+ }
+
+ if (Enum.class.isAssignableFrom(expected) ) {
+ if (guess instanceof String) {
+ for (Object enumConstant : expected.getEnumConstants()) {
+ String target = ((Enum<?>)enumConstant).name();
+ if (target.equals(guess)) return enumConstant;
+ }
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate " + guess + " to an enum of type " + expected, pos);
+ }
+ }
+
+ if (Class.class == expected) {
+ if (guess instanceof String) try {
+ return Class.forName(toFQ((String)guess));
+ } catch (ClassNotFoundException e) {
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate " + guess + " to a class object.", pos);
+ }
+ }
+
+ throw new AnnotationValueDecodeFail(v,
+ "Can't translate a " + guess.getClass() + " to the expected " + expected, pos);
+ }
+
+ /**
+ * Returns the raw expressions used for the provided {@code annotationMethodName}.
+ *
+ * You should use this method for annotation methods that return {@code Class} objects. Remember that
+ * class literals end in ".class" which you probably want to strip off.
+ */
+ public List<String> getRawExpressions(String annotationMethodName) {
+ AnnotationValue v = values.get(annotationMethodName);
+ return v == null ? Collections.<String>emptyList() : v.raws;
+ }
+
+ public boolean isExplicit(String annotationMethodName) {
+ AnnotationValue annotationValue = values.get(annotationMethodName);
+ return annotationValue != null && annotationValue.isExplicit();
+ }
+
+ /**
+ * Convenience method to return the first result in a {@link #getRawExpressions(String)} call.
+ *
+ * You should use this method if the annotation method is not an array type.
+ */
+ public String getRawExpression(String annotationMethodName) {
+ List<String> l = getRawExpressions(annotationMethodName);
+ return l.isEmpty() ? null : l.get(0);
+ }
+
+ /** Generates an error message on the stated annotation value (you should only call this method if you know it's there!) */
+ public void setError(String annotationMethodName, String message) {
+ setError(annotationMethodName, message, -1);
+ }
+
+ /** Generates a warning message on the stated annotation value (you should only call this method if you know it's there!) */
+ public void setWarning(String annotationMethodName, String message) {
+ setWarning(annotationMethodName, message, -1);
+ }
+
+ /** Generates an error message on the stated annotation value, which must have an array initializer.
+ * The index-th item in the initializer will carry the error (you should only call this method if you know it's there!) */
+ public void setError(String annotationMethodName, String message, int index) {
+ AnnotationValue v = values.get(annotationMethodName);
+ if (v == null) return;
+ v.setError(message, index);
+ }
+
+ /** Generates a warning message on the stated annotation value, which must have an array initializer.
+ * The index-th item in the initializer will carry the error (you should only call this method if you know it's there!) */
+ public void setWarning(String annotationMethodName, String message, int index) {
+ AnnotationValue v = values.get(annotationMethodName);
+ if (v == null) return;
+ v.setWarning(message, index);
+ }
+
+ /**
+ * Attempts to translate class literals to their fully qualified names, such as 'Throwable.class' to 'java.lang.Throwable'.
+ *
+ * This process is at best a guess, but it will take into account import statements.
+ */
+ public List<String> getProbableFQTypes(String annotationMethodName) {
+ List<String> result = new ArrayList<String>();
+ AnnotationValue v = values.get(annotationMethodName);
+ if (v == null) return Collections.emptyList();
+
+ for (Object o : v.valueGuesses) result.add(o == null ? null : toFQ(o.toString()));
+ return result;
+ }
+
+ /**
+ * Convenience method to return the first result in a {@link #getProbableFQType(String)} call.
+ *
+ * You should use this method if the annotation method is not an array type.
+ */
+ public String getProbableFQType(String annotationMethodName) {
+ List<String> l = getProbableFQTypes(annotationMethodName);
+ return l.isEmpty() ? null : l.get(0);
+ }
+
+ private String toFQ(String typeName) {
+ Class<?> c;
+ boolean fqn = typeName.indexOf('.') > -1;
+ String prefix = fqn ? typeName.substring(0, typeName.indexOf('.')) : typeName;
+
+ for (String im : ast.getImportStatements()) {
+ int idx = im.lastIndexOf('.');
+ String simple = im;
+ if (idx > -1) simple = im.substring(idx+1);
+ if (simple.equals(prefix)) {
+ return im + typeName.substring(prefix.length());
+ }
+ }
+
+ c = tryClass(typeName);
+ if (c != null) return c.getName();
+
+ c = tryClass("java.lang." + typeName);
+ if (c != null) return c.getName();
+
+ //Try star imports
+ for (String im : ast.getImportStatements()) {
+ if (im.endsWith(".*")) {
+ c = tryClass(im.substring(0, im.length() -1) + typeName);
+ if (c != null) return c.getName();
+ }
+ }
+
+ if (!fqn) {
+ String pkg = ast.getPackageDeclaration();
+ if (pkg != null) return pkg + "." + typeName;
+ }
+
+ return null;
+ }
+
+ private Class<?> tryClass(String name) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java
new file mode 100644
index 00000000..c8ee4c00
--- /dev/null
+++ b/src/core/lombok/core/LombokNode.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import lombok.core.AST.Kind;
+
+/**
+ * An instance of this class wraps an Eclipse/javac internal node object.
+ *
+ * @param A Type of our owning AST.
+ * @param L self-type.
+ * @param N The common type of all AST nodes in the internal representation of the target platform.
+ * For example, JCTree for javac, and ASTNode for Eclipse.
+ */
+public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> {
+ protected final A ast;
+ protected final Kind kind;
+ protected final N node;
+ protected final List<L> children;
+ protected L parent;
+
+ /** This flag has no specified meaning; you can set and retrieve it.
+ *
+ * In practice, for annotation nodes it means: Some AnnotationHandler finished whatever changes were required,
+ * and for all other nodes it means: This node was made by a lombok operation.
+ */
+ protected boolean handled;
+
+ /** structurally significant are those nodes that can be annotated in java 1.6 or are method-like toplevels,
+ * so fields, local declarations, method arguments, methods, types, the Compilation Unit itself, and initializers. */
+ protected boolean isStructurallySignificant;
+
+ /**
+ * Creates a new Node object that represents the provided node.
+ *
+ * @param ast The owning AST - this node is part of this AST's tree of nodes.
+ * @param node The AST object in the target parser's own internal AST tree that this node object will represent.
+ * @param children A list of child nodes. Passing in null results in the children list being empty, not null.
+ * @param kind The kind of node represented by this object.
+ */
+ @SuppressWarnings("unchecked")
+ protected LombokNode(A ast, N node, List<L> children, Kind kind) {
+ this.ast = ast;
+ this.kind = kind;
+ this.node = node;
+ this.children = children == null ? new ArrayList<L>() : children;
+ for (L child : this.children) child.parent = (L) this;
+ this.isStructurallySignificant = calculateIsStructurallySignificant();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return String.format("NODE %s (%s) %s%s",
+ kind, node == null ? "(NULL)" : node.getClass(), handled ? "[HANDLED]" : "", node == null ? "" : node);
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's {@code getPackageDeclaration} method.
+ *
+ * @see AST#getPackageDeclaration()
+ */
+ public String getPackageDeclaration() {
+ return ast.getPackageDeclaration();
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's {@code getImportStatements} method.
+ *
+ * @see AST#getImportStatements()
+ */
+ public Collection<String> getImportStatements() {
+ return ast.getImportStatements();
+ }
+
+ /**
+ * See {@link #isStructurallySignificant}.
+ */
+ protected abstract boolean calculateIsStructurallySignificant();
+
+ /**
+ * Convenient shortcut to the owning ast object's get method.
+ *
+ * @see AST#get(Object)
+ */
+ public L getNodeFor(N obj) {
+ return ast.get(obj);
+ }
+
+ /**
+ * @return The javac/Eclipse internal AST object wrapped by this LombokNode object.
+ */
+ public N get() {
+ return node;
+ }
+
+ /**
+ * Replaces the AST node represented by this node object with the provided node. This node must
+ * have a parent, obviously, for this to work.
+ *
+ * Also affects the underlying (Eclipse/javac) AST.
+ */
+ @SuppressWarnings("unchecked")
+ public L replaceWith(N newN, Kind newNodeKind) {
+ L newNode = ast.buildTree(newN, newNodeKind);
+ newNode.parent = parent;
+ for (int i = 0; i < parent.children.size(); i++) {
+ if (parent.children.get(i) == this) ((List)parent.children).set(i, newNode);
+ }
+
+ parent.replaceChildNode(get(), newN);
+ return newNode;
+ }
+
+ /**
+ * Replaces the stated node with a new one. The old node must be a direct child of this node.
+ *
+ * Also affects the underlying (Eclipse/javac) AST.
+ */
+ public void replaceChildNode(N oldN, N newN) {
+ ast.replaceStatementInNode(get(), oldN, newN);
+ }
+
+ public Kind getKind() {
+ return kind;
+ }
+
+ /**
+ * Return the name of your type (simple name), method, field, or local variable. Return null if this
+ * node doesn't really have a name, such as initializers, while statements, etc.
+ */
+ public abstract String getName();
+
+ /** Returns the structurally significant node that encloses this one.
+ *
+ * @see #isStructurallySignificant()
+ */
+ public L up() {
+ L result = parent;
+ while (result != null && !result.isStructurallySignificant) result = result.parent;
+ return result;
+ }
+
+ /**
+ * Returns the direct parent node in the AST tree of this node. For example, a local variable declaration's
+ * direct parent can be e.g. an If block, but its {@code up()} {@code LombokNode} is the {@code Method} that contains it.
+ */
+ public L directUp() {
+ return parent;
+ }
+
+ /**
+ * Returns all children nodes.
+ *
+ * A copy is created, so changing the list has no effect. Also, while iterating through this list,
+ * you may add, remove, or replace children without causing {@code ConcurrentModificationException}s.
+ */
+ public Collection<L> down() {
+ return new ArrayList<L>(children);
+ }
+
+ /**
+ * returns the value of the 'handled' flag.
+ *
+ * @see #handled
+ */
+ public boolean isHandled() {
+ return handled;
+ }
+
+ /**
+ * Sets the handled flag, then returns itself for chaining.
+ *
+ * @see #handled
+ */
+ @SuppressWarnings("unchecked")
+ public L setHandled() {
+ this.handled = true;
+ return (L)this;
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's top method.
+ *
+ * @see AST#top()
+ */
+ public L top() {
+ return ast.top();
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's getFileName method.
+ *
+ * @see AST#getFileName()
+ */
+ public String getFileName() {
+ return ast.getFileName();
+ }
+
+ /**
+ * Adds the stated node as a direct child of this node.
+ *
+ * Does not change the underlying (javac/Eclipse) AST, only the wrapped view.
+ */
+ @SuppressWarnings("unchecked")
+ public L add(N newChild, Kind newChildKind) {
+ L n = ast.buildTree(newChild, newChildKind);
+ if (n == null) return null;
+ n.parent = (L) this;
+ ((List)children).add(n);
+ return n;
+ }
+
+ /**
+ * Reparses the AST node represented by this node. Any existing nodes that occupy a different space in the AST are rehomed, any
+ * nodes that no longer exist are removed, and new nodes are created.
+ *
+ * Careful - the node you call this on must not itself have been removed or rehomed - it rebuilds <i>all children</i>.
+ */
+ public void rebuild() {
+ Map<N, L> oldNodes = new IdentityHashMap<N, L>();
+ gatherAndRemoveChildren(oldNodes);
+
+ L newNode = ast.buildTree(get(), kind);
+
+ ast.replaceNewWithExistingOld(oldNodes, newNode);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void gatherAndRemoveChildren(Map<N, L> map) {
+ for (L child : children) child.gatherAndRemoveChildren(map);
+ ast.identityDetector.remove(get());
+ map.put(get(), (L) this);
+ children.clear();
+ ast.getNodeMap().remove(get());
+ }
+
+ /**
+ * Removes the stated node, which must be a direct child of this node, from the AST.
+ *
+ * Does not change the underlying (javac/Eclipse) AST, only the wrapped view.
+ */
+ public void removeChild(L child) {
+ children.remove(child);
+ }
+
+ /**
+ * Sets the handled flag on this node, and all child nodes, then returns itself, for chaining.
+ *
+ * @see #handled
+ */
+ @SuppressWarnings("unchecked")
+ public L recursiveSetHandled() {
+ this.handled = true;
+ for (L child : children) child.recursiveSetHandled();
+ return (L) this;
+ }
+
+ /** Generate a compiler error on this node. */
+ public abstract void addError(String message);
+
+ /** Generate a compiler warning on this node. */
+ public abstract void addWarning(String message);
+
+ /**
+ * Structurally significant means: LocalDeclaration, TypeDeclaration, MethodDeclaration, ConstructorDeclaration,
+ * FieldDeclaration, Initializer, and CompilationUnitDeclaration.
+ * The rest is e.g. if statements, while loops, etc.
+ */
+ public boolean isStructurallySignificant() {
+ return isStructurallySignificant;
+ }
+}
diff --git a/src/core/lombok/core/PrintAST.java b/src/core/lombok/core/PrintAST.java
new file mode 100644
index 00000000..df1b652c
--- /dev/null
+++ b/src/core/lombok/core/PrintAST.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Will print the tree structure of annotated node and all its children.
+ *
+ * This annotation is useful only for those working on Lombok, for example to test if a Lombok handlers is doing its
+ * job correctly, or to see what the imagined endresult of a transformation is supposed to look like.
+ */
+@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PrintAST {
+ /**
+ * Normally, the AST is printed to standard out, but you can pick a filename instead. Useful for many IDEs
+ * which don't have a console unless you start them from the command line.
+ */
+ String outfile() default "";
+
+ /**
+ * Sets whether to print node structure (false) or generated java code (true).
+ *
+ * By setting printContent to true, the annotated element's java code representation is printed. If false,
+ * its node structure (e.g. node classname) is printed, and this process is repeated for all children.
+ */
+ boolean printContent() default false;
+}
diff --git a/src/core/lombok/core/SpiLoadUtil.java b/src/core/lombok/core/SpiLoadUtil.java
new file mode 100644
index 00000000..0a97af7e
--- /dev/null
+++ b/src/core/lombok/core/SpiLoadUtil.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import lombok.Lombok;
+
+/**
+ * The java core libraries have a SPI discovery system, but it works only in Java 1.6 and up. For at least Eclipse,
+ * lombok actually works in java 1.5, so we've rolled our own SPI discovery system.
+ *
+ * It is not API compatible with {@code ServiceLoader}.
+ *
+ * @see java.util.ServiceLoader
+ */
+public class SpiLoadUtil {
+ private SpiLoadUtil() {
+ //Prevent instantiation
+ }
+
+ /**
+ * Returns an iterator of instances that, at least according to the spi discovery file, are implementations
+ * of the stated class.
+ *
+ * Like ServiceLoader, each listed class is turned into an instance by calling the public no-args constructor.
+ *
+ * Convenience method that calls the more elaborate {@link #findServices(Class, ClassLoader)} method with
+ * this {@link java.lang.Thread}'s context class loader as {@code ClassLoader}.
+ *
+ * @param target class to find implementations for.
+ */
+ public static <C> Iterable<C> findServices(Class<C> target) throws IOException {
+ return findServices(target, Thread.currentThread().getContextClassLoader());
+ }
+
+ /**
+ * Returns an iterator of class objects that, at least according to the spi discovery file, are implementations
+ * of the stated class.
+ *
+ * Like ServiceLoader, each listed class is turned into an instance by calling the public no-args constructor.
+ *
+ * @param target class to find implementations for.
+ * @param loader The classloader object to use to both the spi discovery files, as well as the loader to use
+ * to make the returned instances.
+ */
+ public static <C> Iterable<C> findServices(final Class<C> target, final ClassLoader loader) throws IOException {
+ Enumeration<URL> resources = loader.getResources("META-INF/services/" + target.getName());
+ final Set<String> entries = new LinkedHashSet<String>();
+ while (resources.hasMoreElements()) {
+ URL url = resources.nextElement();
+ readServicesFromUrl(entries, url);
+ }
+
+ final Iterator<String> names = entries.iterator();
+ return new Iterable<C> () {
+ @Override public Iterator<C> iterator() {
+ return new Iterator<C>() {
+ @Override public boolean hasNext() {
+ return names.hasNext();
+ }
+
+ @Override public C next() {
+ try {
+ return target.cast(Class.forName(names.next(), true, loader).newInstance());
+ } catch (Throwable t) {
+ throw Lombok.sneakyThrow(t);
+ }
+ }
+
+ @Override public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ private static void readServicesFromUrl(Collection<String> list, URL url) throws IOException {
+ InputStream in = url.openStream();
+ try {
+ if (in == null) return;
+ BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ while (true) {
+ String line = r.readLine();
+ if (line == null) break;
+ int idx = line.indexOf('#');
+ if (idx != -1) line = line.substring(0, idx);
+ line = line.trim();
+ if (line.length() == 0) continue;
+ list.add(line);
+ }
+ } finally {
+ try {
+ if (in != null) in.close();
+ } catch (Throwable ignore) {}
+ }
+ }
+
+ /**
+ * This method will find the @{code T} in {@code public class Foo extends BaseType<T>}.
+ *
+ * It returns an annotation type because it is used exclusively to figure out which annotations are
+ * being handled by {@link lombok.eclipse.EclipseAnnotationHandler} and {@link lombok.javac.JavacAnnotationHandler}.
+ */
+ @SuppressWarnings("unchecked")
+ public static Class<? extends Annotation> findAnnotationClass(Class<?> c, Class<?> base) {
+ if (c == Object.class || c == null) return null;
+ for (Type iface : c.getGenericInterfaces()) {
+ if (iface instanceof ParameterizedType) {
+ ParameterizedType p = (ParameterizedType)iface;
+ if (!base.equals(p.getRawType())) continue;
+ Type target = p.getActualTypeArguments()[0];
+ if (target instanceof Class<?>) {
+ if (Annotation.class.isAssignableFrom((Class<?>) target)) {
+ return (Class<? extends Annotation>) target;
+ }
+ }
+
+ throw new ClassCastException("Not an annotation type: " + target);
+ }
+ }
+
+ Class<? extends Annotation> potential = findAnnotationClass(c.getSuperclass(), base);
+ if (potential != null) return potential;
+ for (Class<?> iface : c.getInterfaces()) {
+ potential = findAnnotationClass(iface, base);
+ if (potential != null) return potential;
+ }
+
+ return null;
+ }
+}
diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java
new file mode 100644
index 00000000..6b457927
--- /dev/null
+++ b/src/core/lombok/core/TransformationsUtil.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Container for static utility methods useful for some of the standard lombok transformations, regardless of
+ * target platform (e.g. useful for both javac and Eclipse lombok implementations).
+ */
+public class TransformationsUtil {
+ private TransformationsUtil() {
+ //Prevent instantiation
+ }
+
+ private static final List<String> KNOWN_BOOLEAN_PREFIXES = Collections.unmodifiableList(Arrays.asList(
+ "is", "has", "get"
+ ));
+
+ /**
+ * Generates a getter name from a given field name.
+ *
+ * Strategy:
+ *
+ * First, pick a prefix. 'get' normally, but 'is' if {@code isBoolean} is true.
+ *
+ * Then, check if the first character of the field is lowercase. If so, check if the second character
+ * exists and is title or upper case. If so, uppercase the first character. If not, titlecase the first character.
+ *
+ * return the prefix plus the possibly title/uppercased first character, and the rest of the field name.
+ *
+ * Note that for boolean fields, if the field starts with 'has', 'get', or 'is', and the character after that is
+ * <b>not</b> a lowercase character, the field name is returned without changing any character's case and without
+ * any prefix.
+ *
+ * @param fieldName the name of the field.
+ * @param isBoolean if the field is of type 'boolean'. For fields of type 'java.lang.Boolean', you should provide {@code false}.
+ */
+ public static String toGetterName(CharSequence fieldName, boolean isBoolean) {
+ final String prefix = isBoolean ? "is" : "get";
+
+ if (fieldName.length() == 0) return prefix;
+
+ for (String knownBooleanPrefix : KNOWN_BOOLEAN_PREFIXES) {
+ if (!fieldName.toString().startsWith(knownBooleanPrefix)) continue;
+ if (fieldName.length() > knownBooleanPrefix.length() &&
+ !Character.isLowerCase(fieldName.charAt(knownBooleanPrefix.length()))) {
+ //The field is called something like 'isFoo' or 'hasFoo' or 'getFoo', so we shouldn't
+ //prefix with 'is' but instead just use the field name as is. The isLowerCase check is so we don't turn
+ //hashCodeGenerated, which so happens to start with 'has', into hasHCodeGenerated instead of isHashCodeGenerated.
+ return fieldName.toString();
+ }
+ }
+
+ return buildName(prefix, fieldName.toString());
+ }
+
+ public static final Pattern PRIMITIVE_TYPE_NAME_PATTERN = Pattern.compile(
+ "^(boolean|byte|short|int|long|float|double|char)$");
+
+ public static final Pattern NON_NULL_PATTERN = Pattern.compile("^nonnull$", Pattern.CASE_INSENSITIVE);
+ public static final Pattern NULLABLE_PATTERN = Pattern.compile("^nullable$", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Generates a getter name from a given field name.
+ *
+ * Strategy:
+ *
+ * Check if the first character of the field is lowercase. If so, check if the second character
+ * exists and is title or upper case. If so, uppercase the first character. If not, titlecase the first character.
+ *
+ * return "set" plus the possibly title/uppercased first character, and the rest of the field name.
+ *
+ * @param fieldName the name of the field.
+ */
+ public static String toSetterName(CharSequence fieldName) {
+ return buildName("set", fieldName.toString());
+ }
+
+ private static String buildName(String prefix, String suffix) {
+ if (suffix.length() == 0) return prefix;
+
+ char first = suffix.charAt(0);
+ if (Character.isLowerCase(first)) {
+ boolean useUpperCase = suffix.length() > 2 &&
+ (Character.isTitleCase(suffix.charAt(1)) || Character.isUpperCase(suffix.charAt(1)));
+ suffix = String.format("%s%s",
+ useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first),
+ suffix.subSequence(1, suffix.length()));
+ }
+ return String.format("%s%s", prefix, suffix);
+ }
+
+ public static List<String> toAllGetterNames(CharSequence fieldName, boolean isBoolean) {
+ if (!isBoolean) return Collections.singletonList(toGetterName(fieldName, false));
+
+ List<String> baseNames = new ArrayList<String>();
+ baseNames.add(fieldName.toString());
+ for (String knownBooleanPrefix : KNOWN_BOOLEAN_PREFIXES) {
+ if (!fieldName.toString().startsWith(knownBooleanPrefix)) continue;
+ if (fieldName.length() > knownBooleanPrefix.length() &&
+ !Character.isLowerCase(fieldName.charAt(knownBooleanPrefix.length()))) {
+ //The field is called something like 'isFoo' or 'hasFoo' or 'getFoo', so the practical fieldname
+ //could also be 'foo'.
+ baseNames.add(fieldName.toString().substring(knownBooleanPrefix.length()));
+ //prefix with 'is' but instead just use the field name as is. The isLowerCase check is so we don't turn
+ //hashCodeGenerated, which so happens to start with 'has', into hasHCodeGenerated instead of isHashCodeGenerated.
+ }
+ }
+
+ Set<String> names = new HashSet<String>();
+ for (String baseName : baseNames) {
+ if (baseName.length() > 0 && Character.isLowerCase(baseName.charAt(0))) {
+ baseName = Character.toTitleCase(baseName.charAt(0)) + baseName.substring(1);
+ }
+
+ for (String prefix : KNOWN_BOOLEAN_PREFIXES) {
+ names.add(prefix + baseName);
+ }
+ }
+
+ return new ArrayList<String>(names);
+ }
+}
diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java
new file mode 100644
index 00000000..5de01b70
--- /dev/null
+++ b/src/core/lombok/core/TypeLibrary.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Library of types, which can be used to look up potential matching types.
+ *
+ * For example, if you put 'foo.Spork' and 'bar.Spork' into the library, and then ask for
+ * all compatible types given the type 'Spork', you'll get both of them, but you'll only
+ * get the one if you ask for compatible types given 'foo.Spork'.
+ *
+ * Useful to 'guess' if a given annotation AST node matches an annotation handler's target annotation.
+ */
+public class TypeLibrary {
+ private final Map<String, Set<String>> simpleToQualifiedMap = new HashMap<String, Set<String>>();
+
+ /**
+ * Add a type to the library.
+ *
+ * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'.
+ */
+ public void addType(String fullyQualifiedTypeName) {
+ int idx = fullyQualifiedTypeName.lastIndexOf('.');
+ if (idx == -1) throw new IllegalArgumentException(
+ "Only fully qualified types are allowed (and stuff in the default package is not palatable to us either!)");
+
+ final String simpleName = fullyQualifiedTypeName.substring(idx +1);
+ final String packageName = fullyQualifiedTypeName.substring(0, idx);
+
+ if (simpleToQualifiedMap.put(fullyQualifiedTypeName, Collections.singleton(fullyQualifiedTypeName)) != null) return;
+
+ addToMap(simpleName, fullyQualifiedTypeName);
+ addToMap(packageName + ".*", fullyQualifiedTypeName);
+ }
+
+ private TypeLibrary addToMap(String keyName, String fullyQualifiedTypeName) {
+ Set<String> existing = simpleToQualifiedMap.get(keyName);
+ Set<String> set = (existing == null) ? new HashSet<String>() : new HashSet<String>(existing);
+ set.add(fullyQualifiedTypeName);
+ simpleToQualifiedMap.put(keyName, Collections.unmodifiableSet(set));
+ return this;
+ }
+
+ /**
+ * Returns all items in the type library that may be a match to the provided type.
+ *
+ * @param typeReference something like 'String' or even 'java.lang.String'.
+ */
+ public Collection<String> findCompatible(String typeReference) {
+ Set<String> result = simpleToQualifiedMap.get(typeReference);
+ return result == null ? Collections.<String>emptySet() : result;
+ }
+}
diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java
new file mode 100644
index 00000000..dd1d9a53
--- /dev/null
+++ b/src/core/lombok/core/TypeResolver.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import lombok.core.AST.Kind;
+
+/**
+ * Capable of resolving a simple type name such as 'String' into 'java.lang.String'.
+ */
+public class TypeResolver {
+ private final TypeLibrary library;
+ private Collection<String> imports;
+
+ /**
+ * Creates a new TypeResolver that can be used to resolve types in a given library, encountered in
+ * a source file with the provided package and import statements.
+ */
+ public TypeResolver(TypeLibrary library, String packageString, Collection<String> importStrings) {
+ this.library = library;
+ this.imports = makeImportList(packageString, importStrings);
+ }
+
+ private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) {
+ Set<String> imports = new HashSet<String>();
+ if (packageString != null) imports.add(packageString + ".*");
+ imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings);
+ return imports;
+ }
+
+ /**
+ * Finds type matches for the stated type reference. The provided context is scanned for local type names
+ * that shadow type names listed in import statements. If such a shadowing occurs, no matches are returned
+ * for any shadowed types, as you would expect.
+ */
+ public Collection<String> findTypeMatches(LombokNode<?, ?, ?> context, String typeRef) {
+ Collection<String> potentialMatches = library.findCompatible(typeRef);
+ if (potentialMatches.isEmpty()) return Collections.emptyList();
+
+ int idx = typeRef.indexOf('.');
+ if (idx > -1) return potentialMatches;
+ String simpleName = typeRef.substring(idx+1);
+
+ //If there's an import statement that explicitly imports a 'Getter' that isn't any of our potentials, return no matches.
+ if (nameConflictInImportList(simpleName, potentialMatches)) return Collections.emptyList();
+
+ //Check if any of our potentials is even imported in the first place. If not: no matches.
+ potentialMatches = eliminateImpossibleMatches(potentialMatches);
+ if (potentialMatches.isEmpty()) return Collections.emptyList();
+
+ //Find a lexically accessible type of the same simple name in the same Compilation Unit. If it exists: no matches.
+ LombokNode<?, ?, ?> n = context;
+ while (n != null) {
+ if (n.getKind() == Kind.TYPE) {
+ String name = n.getName();
+ if (name != null && name.equals(simpleName)) return Collections.emptyList();
+ }
+ n = n.up();
+ }
+
+ // The potential matches we found by comparing the import statements is our matching set. Return it.
+ return potentialMatches;
+ }
+
+ private Collection<String> eliminateImpossibleMatches(Collection<String> potentialMatches) {
+ Set<String> results = new HashSet<String>();
+
+ for (String importedType : imports) {
+ Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType));
+ reduced.retainAll(potentialMatches);
+ results.addAll(reduced);
+ }
+
+ return results;
+ }
+
+ private boolean nameConflictInImportList(String simpleName, Collection<String> potentialMatches) {
+ for (String importedType : imports) {
+ if (!toSimpleName(importedType).equals(simpleName)) continue;
+ if (potentialMatches.contains(importedType)) continue;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static String toSimpleName(String typeName) {
+ int idx = typeName.lastIndexOf('.');
+ return idx == -1 ? typeName : typeName.substring(idx+1);
+ }
+}
diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java
new file mode 100644
index 00000000..37944218
--- /dev/null
+++ b/src/core/lombok/core/Version.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+/**
+ * This class just holds lombok's current version.
+ */
+public class Version {
+ // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries).
+ private static final String VERSION = "0.9.2-HEAD";
+
+ private Version() {
+ //Prevent instantiation
+ }
+
+ /**
+ * Prints the version followed by a newline, and exits.
+ */
+ public static void main(String[] args) {
+ System.out.println(VERSION);
+ }
+
+ /**
+ * Get the current Lombok version.
+ */
+ public static String getVersion() {
+ return VERSION;
+ }
+}
diff --git a/src/core/lombok/core/package-info.java b/src/core/lombok/core/package-info.java
new file mode 100644
index 00000000..0dc5225c
--- /dev/null
+++ b/src/core/lombok/core/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Contains the platform-agnostic core of lombok.
+ * Includes the lombok AST superclasses, annotation introspection support,
+ * an implementation of SPI service loader (to avoid being dependent on a v1.6 JVM),
+ * lombok's version, and annotations and support classes for your normal java code
+ * that's primarily useful for developing and debugging lombok.
+ */
+package lombok.core;
diff --git a/src/core/lombok/eclipse/Eclipse.java b/src/core/lombok/eclipse/Eclipse.java
new file mode 100644
index 00000000..41d9300f
--- /dev/null
+++ b/src/core/lombok/eclipse/Eclipse.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Lombok;
+import lombok.core.AnnotationValues;
+import lombok.core.TypeLibrary;
+import lombok.core.TypeResolver;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues.AnnotationValue;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.Literal;
+import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Wildcard;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.osgi.framework.Bundle;
+
+public class Eclipse {
+ /**
+ * Eclipse's Parser class is instrumented to not attempt to fill in the body of any method or initializer
+ * or field initialization if this flag is set. Set it on the flag field of
+ * any method, field, or initializer you create!
+ */
+ public static final int ECLIPSE_DO_NOT_TOUCH_FLAG = ASTNode.Bit24;
+
+ private Eclipse() {
+ //Prevent instantiation
+ }
+
+ private static final String DEFAULT_BUNDLE = "org.eclipse.jdt.core";
+
+ /**
+ * Generates an error in the Eclipse error log. Note that most people never look at it!
+ */
+ public static void error(CompilationUnitDeclaration cud, String message) {
+ error(cud, message, DEFAULT_BUNDLE, null);
+ }
+
+ /**
+ * Generates an error in the Eclipse error log. Note that most people never look at it!
+ */
+ public static void error(CompilationUnitDeclaration cud, String message, Throwable error) {
+ error(cud, message, DEFAULT_BUNDLE, error);
+ }
+
+ /**
+ * Generates an error in the Eclipse error log. Note that most people never look at it!
+ */
+ public static void error(CompilationUnitDeclaration cud, String message, String bundleName) {
+ error(cud, message, bundleName, null);
+ }
+
+ /**
+ * Generates an error in the Eclipse error log. Note that most people never look at it!
+ */
+ public static void error(CompilationUnitDeclaration cud, String message, String bundleName, Throwable error) {
+ Bundle bundle = Platform.getBundle(bundleName);
+ if (bundle == null) {
+ System.err.printf("Can't find bundle %s while trying to report error:\n%s\n", bundleName, message);
+ return;
+ }
+
+ ILog log = Platform.getLog(bundle);
+
+ log.log(new Status(IStatus.ERROR, bundleName, message, error));
+ if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0);
+ }
+
+ /**
+ * For 'speed' reasons, Eclipse works a lot with char arrays. I have my doubts this was a fruitful exercise,
+ * but we need to deal with it. This turns [[java][lang][String]] into "java.lang.String".
+ */
+ public static String toQualifiedName(char[][] typeName) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (char[] c : typeName) {
+ sb.append(first ? "" : ".").append(c);
+ first = false;
+ }
+ return sb.toString();
+ }
+
+ public static char[][] fromQualifiedName(String typeName) {
+ String[] split = typeName.split("\\.");
+ char[][] result = new char[split.length][];
+ for (int i = 0; i < split.length; i++) {
+ result[i] = split[i].toCharArray();
+ }
+ return result;
+ }
+
+
+ /**
+ * You can't share TypeParameter objects or bad things happen; for example, one 'T' resolves differently
+ * from another 'T', even for the same T in a single class file. Unfortunately the TypeParameter type hierarchy
+ * is complicated and there's no clone method on TypeParameter itself. This method can clone them.
+ */
+ public static TypeParameter[] copyTypeParams(TypeParameter[] params, ASTNode source) {
+ if (params == null) return null;
+ TypeParameter[] out = new TypeParameter[params.length];
+ int idx = 0;
+ for (TypeParameter param : params) {
+ TypeParameter o = new TypeParameter();
+ setGeneratedBy(o, source);
+ o.annotations = param.annotations;
+ o.bits = param.bits;
+ o.modifiers = param.modifiers;
+ o.name = param.name;
+ o.type = copyType(param.type, source);
+ o.sourceStart = param.sourceStart;
+ o.sourceEnd = param.sourceEnd;
+ o.declarationEnd = param.declarationEnd;
+ o.declarationSourceStart = param.declarationSourceStart;
+ o.declarationSourceEnd = param.declarationSourceEnd;
+ if (param.bounds != null) {
+ TypeReference[] b = new TypeReference[param.bounds.length];
+ int idx2 = 0;
+ for (TypeReference ref : param.bounds) b[idx2++] = copyType(ref, source);
+ o.bounds = b;
+ }
+ out[idx++] = o;
+ }
+ return out;
+ }
+
+ /**
+ * Convenience method that creates a new array and copies each TypeReference in the source array via
+ * {@link #copyType(TypeReference, ASTNode)}.
+ */
+ public static TypeReference[] copyTypes(TypeReference[] refs, ASTNode source) {
+ if (refs == null) return null;
+ TypeReference[] outs = new TypeReference[refs.length];
+ int idx = 0;
+ for (TypeReference ref : refs) {
+ outs[idx++] = copyType(ref, source);
+ }
+ return outs;
+ }
+
+ /**
+ * You can't share TypeReference objects or subtle errors start happening.
+ * Unfortunately the TypeReference type hierarchy is complicated and there's no clone
+ * method on TypeReference itself. This method can clone them.
+ */
+ public static TypeReference copyType(TypeReference ref, ASTNode source) {
+ if (ref instanceof ParameterizedQualifiedTypeReference) {
+ ParameterizedQualifiedTypeReference iRef = (ParameterizedQualifiedTypeReference) ref;
+ TypeReference[][] args = null;
+ if (iRef.typeArguments != null) {
+ args = new TypeReference[iRef.typeArguments.length][];
+ int idx = 0;
+ for (TypeReference[] inRefArray : iRef.typeArguments) {
+ if (inRefArray == null) args[idx++] = null;
+ else {
+ TypeReference[] outRefArray = new TypeReference[inRefArray.length];
+ int idx2 = 0;
+ for (TypeReference inRef : inRefArray) {
+ outRefArray[idx2++] = copyType(inRef, source);
+ }
+ args[idx++] = outRefArray;
+ }
+ }
+ }
+ TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), iRef.sourcePositions);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ if (ref instanceof ArrayQualifiedTypeReference) {
+ ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref;
+ TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), iRef.sourcePositions);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ if (ref instanceof QualifiedTypeReference) {
+ QualifiedTypeReference iRef = (QualifiedTypeReference) ref;
+ TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, iRef.sourcePositions);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ if (ref instanceof ParameterizedSingleTypeReference) {
+ ParameterizedSingleTypeReference iRef = (ParameterizedSingleTypeReference) ref;
+ TypeReference[] args = null;
+ if (iRef.typeArguments != null) {
+ args = new TypeReference[iRef.typeArguments.length];
+ int idx = 0;
+ for (TypeReference inRef : iRef.typeArguments) {
+ if (inRef == null) args[idx++] = null;
+ else args[idx++] = copyType(inRef, source);
+ }
+ }
+
+ TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ if (ref instanceof ArrayTypeReference) {
+ ArrayTypeReference iRef = (ArrayTypeReference) ref;
+ TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ if (ref instanceof Wildcard) {
+ Wildcard wildcard = new Wildcard(((Wildcard)ref).kind);
+ wildcard.sourceStart = ref.sourceStart;
+ wildcard.sourceEnd = ref.sourceEnd;
+ setGeneratedBy(wildcard, source);
+ return wildcard;
+ }
+
+ if (ref instanceof SingleTypeReference) {
+ SingleTypeReference iRef = (SingleTypeReference) ref;
+ TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd);
+ setGeneratedBy(typeRef, source);
+ return typeRef;
+ }
+
+ return ref;
+ }
+
+ public static Annotation[] copyAnnotations(Annotation[] annotations, ASTNode source) {
+ return copyAnnotations(annotations, null, source);
+ }
+
+ public static Annotation[] copyAnnotations(Annotation[] annotations1, Annotation[] annotations2, ASTNode source) {
+ if (annotations1 == null && annotations2 == null) return null;
+ if (annotations1 == null) annotations1 = new Annotation[0];
+ if (annotations2 == null) annotations2 = new Annotation[0];
+ Annotation[] outs = new Annotation[annotations1.length + annotations2.length];
+ int idx = 0;
+ for (Annotation annotation : annotations1) {
+ outs[idx++] = copyAnnotation(annotation, source);
+ }
+ for (Annotation annotation : annotations2) {
+ outs[idx++] = copyAnnotation(annotation, source);
+ }
+ return outs;
+ }
+
+ public static Annotation copyAnnotation(Annotation annotation, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+
+ if (annotation instanceof MarkerAnnotation) {
+ MarkerAnnotation ann = new MarkerAnnotation(copyType(annotation.type, source), pS);
+ setGeneratedBy(ann, source);
+ ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = pE;
+ return ann;
+ }
+
+ if (annotation instanceof SingleMemberAnnotation) {
+ SingleMemberAnnotation ann = new SingleMemberAnnotation(copyType(annotation.type, source), pS);
+ setGeneratedBy(ann, source);
+ ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = pE;
+ //TODO memberValue(s) need to be copied as well (same for copying a NormalAnnotation as below).
+ ann.memberValue = ((SingleMemberAnnotation)annotation).memberValue;
+ return ann;
+ }
+
+ if (annotation instanceof NormalAnnotation) {
+ NormalAnnotation ann = new NormalAnnotation(copyType(annotation.type, source), pS);
+ setGeneratedBy(ann, source);
+ ann.declarationSourceEnd = ann.statementEnd = ann.sourceEnd = pE;
+ ann.memberValuePairs = ((NormalAnnotation)annotation).memberValuePairs;
+ return ann;
+ }
+
+ return annotation;
+ }
+
+ /**
+ * Checks if the provided annotation type is likely to be the intended type for the given annotation node.
+ *
+ * This is a guess, but a decent one.
+ */
+ public static boolean annotationTypeMatches(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) {
+ if (node.getKind() != Kind.ANNOTATION) return false;
+ TypeReference typeRef = ((Annotation)node.get()).type;
+ if (typeRef == null || typeRef.getTypeName() == null) return false;
+ String typeName = toQualifiedName(typeRef.getTypeName());
+
+ TypeLibrary library = new TypeLibrary();
+ library.addType(type.getName());
+ TypeResolver resolver = new TypeResolver(library, node.getPackageDeclaration(), node.getImportStatements());
+ Collection<String> typeMatches = resolver.findTypeMatches(node, typeName);
+
+ for (String match : typeMatches) {
+ if (match.equals(type.getName())) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Provides AnnotationValues with the data it needs to do its thing.
+ */
+ public static <A extends java.lang.annotation.Annotation> AnnotationValues<A>
+ createAnnotation(Class<A> type, final EclipseNode annotationNode) {
+ final Annotation annotation = (Annotation) annotationNode.get();
+ Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>();
+
+ final MemberValuePair[] pairs = annotation.memberValuePairs();
+ for (Method m : type.getDeclaredMethods()) {
+ if (!Modifier.isPublic(m.getModifiers())) continue;
+ String name = m.getName();
+ List<String> raws = new ArrayList<String>();
+ List<Object> guesses = new ArrayList<Object>();
+ Expression fullExpression = null;
+ Expression[] expressions = null;
+
+ if (pairs != null) for (MemberValuePair pair : pairs) {
+ char[] n = pair.name;
+ String mName = n == null ? "value" : new String(pair.name);
+ if (mName.equals(name)) fullExpression = pair.value;
+ }
+
+ boolean isExplicit = fullExpression != null;
+
+ if (isExplicit) {
+ if (fullExpression instanceof ArrayInitializer) {
+ expressions = ((ArrayInitializer)fullExpression).expressions;
+ } else expressions = new Expression[] { fullExpression };
+ if (expressions != null) for (Expression ex : expressions) {
+ StringBuffer sb = new StringBuffer();
+ ex.print(0, sb);
+ raws.add(sb.toString());
+ guesses.add(calculateValue(ex));
+ }
+ }
+
+ final Expression fullExpr = fullExpression;
+ final Expression[] exprs = expressions;
+
+ values.put(name, new AnnotationValue(annotationNode, raws, guesses, isExplicit) {
+ @Override public void setError(String message, int valueIdx) {
+ Expression ex;
+ if (valueIdx == -1) ex = fullExpr;
+ else ex = exprs != null ? exprs[valueIdx] : null;
+
+ if (ex == null) ex = annotation;
+
+ int sourceStart = ex.sourceStart;
+ int sourceEnd = ex.sourceEnd;
+
+ annotationNode.addError(message, sourceStart, sourceEnd);
+ }
+
+ @Override public void setWarning(String message, int valueIdx) {
+ Expression ex;
+ if (valueIdx == -1) ex = fullExpr;
+ else ex = exprs != null ? exprs[valueIdx] : null;
+
+ if (ex == null) ex = annotation;
+
+ int sourceStart = ex.sourceStart;
+ int sourceEnd = ex.sourceEnd;
+
+ annotationNode.addWarning(message, sourceStart, sourceEnd);
+ }
+ });
+ }
+
+ return new AnnotationValues<A>(type, values, annotationNode);
+ }
+
+ private static Object calculateValue(Expression e) {
+ if (e instanceof Literal) {
+ ((Literal)e).computeConstant();
+ switch (e.constant.typeID()) {
+ case TypeIds.T_int: return e.constant.intValue();
+ case TypeIds.T_byte: return e.constant.byteValue();
+ case TypeIds.T_short: return e.constant.shortValue();
+ case TypeIds.T_char: return e.constant.charValue();
+ case TypeIds.T_float: return e.constant.floatValue();
+ case TypeIds.T_double: return e.constant.doubleValue();
+ case TypeIds.T_boolean: return e.constant.booleanValue();
+ case TypeIds.T_long: return e.constant.longValue();
+ case TypeIds.T_JavaLangString: return e.constant.stringValue();
+ default: return null;
+ }
+ } else if (e instanceof ClassLiteralAccess) {
+ return Eclipse.toQualifiedName(((ClassLiteralAccess)e).type.getTypeName());
+ } else if (e instanceof SingleNameReference) {
+ return new String(((SingleNameReference)e).token);
+ } else if (e instanceof QualifiedNameReference) {
+ String qName = Eclipse.toQualifiedName(((QualifiedNameReference)e).tokens);
+ int idx = qName.lastIndexOf('.');
+ return idx == -1 ? qName : qName.substring(idx+1);
+ }
+
+ return null;
+ }
+
+ private static Field generatedByField;
+
+ static {
+ try {
+ generatedByField = ASTNode.class.getDeclaredField("$generatedBy");
+ } catch (Throwable t) {
+ throw Lombok.sneakyThrow(t);
+ }
+ }
+
+ public static ASTNode getGeneratedBy(ASTNode node) {
+ try {
+ return (ASTNode) generatedByField.get(node);
+ } catch (Exception t) {
+ throw Lombok.sneakyThrow(t);
+ }
+ }
+
+ public static boolean isGenerated(ASTNode node) {
+ return getGeneratedBy(node) != null;
+ }
+
+ public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) {
+ try {
+ generatedByField.set(node, source);
+ } catch (Exception t) {
+ throw Lombok.sneakyThrow(t);
+ }
+
+ return node;
+ }
+}
diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java
new file mode 100644
index 00000000..e42e5de2
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseAST.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.core.AST;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ImportReference;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
+import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+/**
+ * Wraps around Eclipse's internal AST view to add useful features as well as the ability to visit parents from children,
+ * something Eclipse own AST system does not offer.
+ */
+public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
+ /**
+ * Creates a new EclipseAST of the provided Compilation Unit.
+ *
+ * @param ast The compilation unit, which serves as the top level node in the tree to be built.
+ */
+ public EclipseAST(CompilationUnitDeclaration ast) {
+ super(toFileName(ast));
+ this.compilationUnitDeclaration = ast;
+ setTop(buildCompilationUnit(ast));
+ this.completeParse = isComplete(ast);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getPackageDeclaration() {
+ CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
+ ImportReference pkg = cud.currentPackage;
+ return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName());
+ }
+
+ /** {@inheritDoc} */
+ @Override public Collection<String> getImportStatements() {
+ List<String> imports = new ArrayList<String>();
+ CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
+ if (cud.imports == null) return imports;
+ for (ImportReference imp : cud.imports) {
+ if (imp == null) continue;
+ imports.add(Eclipse.toQualifiedName(imp.getImportName()));
+ }
+
+ return imports;
+ }
+
+ /**
+ * Runs through the entire AST, starting at the compilation unit, calling the provided visitor's visit methods
+ * for each node, depth first.
+ */
+ public void traverse(EclipseASTVisitor visitor) {
+ top().traverse(visitor);
+ }
+
+ void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) {
+ for (EclipseNode child : node.down()) {
+ child.traverse(visitor);
+ }
+ }
+
+ /**
+ * Eclipse starts off with a 'diet' parse which leaves method bodies blank, amongst other shortcuts.
+ *
+ * For such diet parses, this method returns false, otherwise it returns true. Any lombok processor
+ * that needs the contents of methods should just do nothing (and return false so it gets another shot later!)
+ * when this is false.
+ */
+ public boolean isCompleteParse() {
+ return completeParse;
+ }
+
+ class ParseProblem {
+ final boolean isWarning;
+ final String message;
+ final int sourceStart;
+ final int sourceEnd;
+
+ ParseProblem(boolean isWarning, String message, int sourceStart, int sourceEnd) {
+ this.isWarning = isWarning;
+ this.message = message;
+ this.sourceStart = sourceStart;
+ this.sourceEnd = sourceEnd;
+ }
+
+ void addToCompilationResult() {
+ addProblemToCompilationResult((CompilationUnitDeclaration) top().get(),
+ isWarning, message, sourceStart, sourceEnd);
+ }
+ }
+
+ private void propagateProblems() {
+ if (queuedProblems.isEmpty()) return;
+ CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
+ if (cud.compilationResult == null) return;
+ for (ParseProblem problem : queuedProblems) problem.addToCompilationResult();
+ queuedProblems.clear();
+ }
+
+ private final List<ParseProblem> queuedProblems = new ArrayList<ParseProblem>();
+
+ void addProblem(ParseProblem problem) {
+ queuedProblems.add(problem);
+ propagateProblems();
+ }
+
+ /**
+ * Adds a problem to the provided CompilationResult object so that it will show up
+ * in the Problems/Warnings view.
+ */
+ static void addProblemToCompilationResult(CompilationUnitDeclaration ast,
+ boolean isWarning, String message, int sourceStart, int sourceEnd) {
+ if (ast.compilationResult == null) return;
+ char[] fileNameArray = ast.getFileName();
+ if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray();
+ int lineNumber = 0;
+ int columnNumber = 1;
+ CompilationResult result = ast.compilationResult;
+ int[] lineEnds = null;
+ lineNumber = sourceStart >= 0
+ ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1)
+ : 0;
+ columnNumber = sourceStart >= 0
+ ? Util.searchColumnNumber(result.getLineSeparatorPositions(), lineNumber,sourceStart)
+ : 0;
+
+ CategorizedProblem ecProblem = new LombokProblem(
+ fileNameArray, message, 0, new String[0],
+ isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error,
+ sourceStart, sourceEnd, lineNumber, columnNumber);
+ ast.compilationResult.record(ecProblem, null);
+ }
+
+ private static class LombokProblem extends DefaultProblem {
+ private static final String MARKER_ID = "org.eclipse.jdt.apt.pluggable.core.compileProblem"; //$NON-NLS-1$
+
+ public LombokProblem(char[] originatingFileName, String message, int id,
+ String[] stringArguments, int severity,
+ int startPosition, int endPosition, int line, int column) {
+ super(originatingFileName, message, id, stringArguments, severity, startPosition, endPosition, line, column);
+ }
+
+ @Override public int getCategoryID() {
+ return CAT_UNSPECIFIED;
+ }
+
+ @Override public String getMarkerType() {
+ return MARKER_ID;
+ }
+ }
+
+ private final CompilationUnitDeclaration compilationUnitDeclaration;
+ private boolean completeParse;
+
+ private static String toFileName(CompilationUnitDeclaration ast) {
+ return ast.compilationResult.fileName == null ? null : new String(ast.compilationResult.fileName);
+ }
+
+ /**
+ * Call this method to move an EclipseAST generated for a diet parse to rebuild itself for the full parse -
+ * with filled in method bodies and such. Also propagates problems and errors, which in diet parse
+ * mode can't be reliably added to the problems/warnings view.
+ */
+ public void reparse() {
+ propagateProblems();
+ if (completeParse) return;
+ boolean newCompleteParse = isComplete(compilationUnitDeclaration);
+ if (!newCompleteParse) return;
+
+ top().rebuild();
+
+ this.completeParse = true;
+ }
+
+ private static boolean isComplete(CompilationUnitDeclaration unit) {
+ return (unit.bits & ASTNode.HasAllMethodBodies) != 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected EclipseNode buildTree(ASTNode node, Kind kind) {
+ switch (kind) {
+ case COMPILATION_UNIT:
+ return buildCompilationUnit((CompilationUnitDeclaration) node);
+ case TYPE:
+ return buildType((TypeDeclaration) node);
+ case FIELD:
+ return buildField((FieldDeclaration) node);
+ case INITIALIZER:
+ return buildInitializer((Initializer) node);
+ case METHOD:
+ return buildMethod((AbstractMethodDeclaration) node);
+ case ARGUMENT:
+ return buildLocal((Argument) node, kind);
+ case LOCAL:
+ return buildLocal((LocalDeclaration) node, kind);
+ case STATEMENT:
+ return buildStatement((Statement) node);
+ case ANNOTATION:
+ return buildAnnotation((Annotation) node);
+ default:
+ throw new AssertionError("Did not expect to arrive here: " + kind);
+ }
+ }
+
+ private EclipseNode buildCompilationUnit(CompilationUnitDeclaration top) {
+ if (setAndGetAsHandled(top)) return null;
+ List<EclipseNode> children = buildTypes(top.types);
+ return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT));
+ }
+
+ private void addIfNotNull(Collection<EclipseNode> collection, EclipseNode n) {
+ if (n != null) collection.add(n);
+ }
+
+ private List<EclipseNode> buildTypes(TypeDeclaration[] children) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type));
+ return childNodes;
+ }
+
+ private EclipseNode buildType(TypeDeclaration type) {
+ if (setAndGetAsHandled(type)) return null;
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ childNodes.addAll(buildFields(type.fields));
+ childNodes.addAll(buildTypes(type.memberTypes));
+ childNodes.addAll(buildMethods(type.methods));
+ childNodes.addAll(buildAnnotations(type.annotations));
+ return putInMap(new EclipseNode(this, type, childNodes, Kind.TYPE));
+ }
+
+ private Collection<EclipseNode> buildFields(FieldDeclaration[] children) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child));
+ return childNodes;
+ }
+
+ private static <T> List<T> singleton(T item) {
+ List<T> list = new ArrayList<T>();
+ if (item != null) list.add(item);
+ return list;
+ }
+
+ private EclipseNode buildField(FieldDeclaration field) {
+ if (field instanceof Initializer) return buildInitializer((Initializer)field);
+ if (setAndGetAsHandled(field)) return null;
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ addIfNotNull(childNodes, buildStatement(field.initialization));
+ childNodes.addAll(buildAnnotations(field.annotations));
+ return putInMap(new EclipseNode(this, field, childNodes, Kind.FIELD));
+ }
+
+ private EclipseNode buildInitializer(Initializer initializer) {
+ if (setAndGetAsHandled(initializer)) return null;
+ return putInMap(new EclipseNode(this, initializer, singleton(buildStatement(initializer.block)), Kind.INITIALIZER));
+ }
+
+ private Collection<EclipseNode> buildMethods(AbstractMethodDeclaration[] children) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method));
+ return childNodes;
+ }
+
+ private EclipseNode buildMethod(AbstractMethodDeclaration method) {
+ if (setAndGetAsHandled(method)) return null;
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ childNodes.addAll(buildArguments(method.arguments));
+ childNodes.addAll(buildStatements(method.statements));
+ childNodes.addAll(buildAnnotations(method.annotations));
+ return putInMap(new EclipseNode(this, method, childNodes, Kind.METHOD));
+ }
+
+ //Arguments are a kind of LocalDeclaration. They can definitely contain lombok annotations, so we care about them.
+ private Collection<EclipseNode> buildArguments(Argument[] children) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ if (children != null) for (LocalDeclaration local : children) {
+ addIfNotNull(childNodes, buildLocal(local, Kind.ARGUMENT));
+ }
+ return childNodes;
+ }
+
+ private EclipseNode buildLocal(LocalDeclaration local, Kind kind) {
+ if (setAndGetAsHandled(local)) return null;
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ addIfNotNull(childNodes, buildStatement(local.initialization));
+ childNodes.addAll(buildAnnotations(local.annotations));
+ return putInMap(new EclipseNode(this, local, childNodes, kind));
+ }
+
+ private Collection<EclipseNode> buildAnnotations(Annotation[] annotations) {
+ List<EclipseNode> elements = new ArrayList<EclipseNode>();
+ if (annotations != null) for (Annotation an : annotations) addIfNotNull(elements, buildAnnotation(an));
+ return elements;
+ }
+
+ private EclipseNode buildAnnotation(Annotation annotation) {
+ if (annotation == null) return null;
+ if (setAndGetAsHandled(annotation)) return null;
+ return putInMap(new EclipseNode(this, annotation, null, Kind.ANNOTATION));
+ }
+
+ private Collection<EclipseNode> buildStatements(Statement[] children) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ if (children != null) for (Statement child : children) addIfNotNull(childNodes, buildStatement(child));
+ return childNodes;
+ }
+
+ private EclipseNode buildStatement(Statement child) {
+ if (child == null) return null;
+ if (child instanceof TypeDeclaration) return buildType((TypeDeclaration)child);
+
+ if (child instanceof LocalDeclaration) return buildLocal((LocalDeclaration)child, Kind.LOCAL);
+
+ if (setAndGetAsHandled(child)) return null;
+
+ return drill(child);
+ }
+
+ private EclipseNode drill(Statement statement) {
+ List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
+ for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(EclipseNode.class, statement, fa));
+ return putInMap(new EclipseNode(this, statement, childNodes, Kind.STATEMENT));
+ }
+
+ /** For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't
+ * entirely correct according to the JLS spec (only some expressions can be used as statements, not all of them). */
+ @Override protected Collection<Class<? extends ASTNode>> getStatementTypes() {
+ return Collections.<Class<? extends ASTNode>>singleton(Statement.class);
+ }
+}
diff --git a/src/core/lombok/eclipse/EclipseASTAdapter.java b/src/core/lombok/eclipse/EclipseASTAdapter.java
new file mode 100644
index 00000000..2062619c
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseASTAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+
+/**
+ * Standard adapter for the {@link EclipseASTVisitor} interface. Every method on that interface
+ * has been implemented with an empty body. Override whichever methods you need.
+ */
+public abstract class EclipseASTAdapter implements EclipseASTVisitor {
+ /** {@inheritDoc} */
+ public void visitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit) {}
+
+ /** {@inheritDoc} */
+ public void endVisitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit) {}
+
+ /** {@inheritDoc} */
+ public void visitType(EclipseNode typeNode, TypeDeclaration type) {}
+
+ /** {@inheritDoc} */
+ public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) {}
+
+ /** {@inheritDoc} */
+ public void endVisitType(EclipseNode typeNode, TypeDeclaration type) {}
+
+ /** {@inheritDoc} */
+ public void visitInitializer(EclipseNode initializerNode, Initializer initializer) {}
+
+ /** {@inheritDoc} */
+ public void endVisitInitializer(EclipseNode initializerNode, Initializer initializer) {}
+
+ /** {@inheritDoc} */
+ public void visitField(EclipseNode fieldNode, FieldDeclaration field) {}
+
+ /** {@inheritDoc} */
+ public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) {}
+
+ /** {@inheritDoc} */
+ public void endVisitField(EclipseNode fieldNode, FieldDeclaration field) {}
+
+ /** {@inheritDoc} */
+ public void visitMethod(EclipseNode methodNode, AbstractMethodDeclaration method) {}
+
+ /** {@inheritDoc} */
+ public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {}
+
+ /** {@inheritDoc} */
+ public void endVisitMethod(EclipseNode methodNode, AbstractMethodDeclaration method) {}
+
+ /** {@inheritDoc} */
+ public void visitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method) {}
+
+ /** {@inheritDoc} */
+ public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {}
+
+ /** {@inheritDoc} */
+ public void endVisitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method) {}
+
+ /** {@inheritDoc} */
+ public void visitLocal(EclipseNode localNode, LocalDeclaration local) {}
+
+ /** {@inheritDoc} */
+ public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) {}
+
+ /** {@inheritDoc} */
+ public void endVisitLocal(EclipseNode localNode, LocalDeclaration local) {}
+
+ /** {@inheritDoc} */
+ public void visitStatement(EclipseNode statementNode, Statement statement) {}
+
+ /** {@inheritDoc} */
+ public void endVisitStatement(EclipseNode statementNode, Statement statement) {}
+}
diff --git a/src/core/lombok/eclipse/EclipseASTVisitor.java b/src/core/lombok/eclipse/EclipseASTVisitor.java
new file mode 100644
index 00000000..793166d7
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseASTVisitor.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import java.io.PrintStream;
+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.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+
+/**
+ * Implement so you can ask any JavacAST.Node to traverse depth-first through all children,
+ * calling the appropriate visit and endVisit methods.
+ */
+public interface EclipseASTVisitor {
+ /**
+ * Called at the very beginning and end.
+ */
+ void visitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit);
+ void endVisitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit);
+
+ /**
+ * Called when visiting a type (a class, interface, annotation, enum, etcetera).
+ */
+ void visitType(EclipseNode typeNode, TypeDeclaration type);
+ void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation);
+ void endVisitType(EclipseNode typeNode, TypeDeclaration type);
+
+ /**
+ * Called when visiting a field of a class.
+ * Even though in Eclipse initializers (both instance and static) are represented as Initializer objects,
+ * which are a subclass of FieldDeclaration, those do NOT result in a call to this method. They result
+ * in a call to the visitInitializer method.
+ */
+ void visitField(EclipseNode fieldNode, FieldDeclaration field);
+ void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation);
+ void endVisitField(EclipseNode fieldNode, FieldDeclaration field);
+
+ /**
+ * Called for static and instance initializers. You can tell the difference via the modifier flag on the
+ * ASTNode (8 for static, 0 for not static). The content is in the 'block', not in the 'initialization',
+ * which would always be null for an initializer instance.
+ */
+ void visitInitializer(EclipseNode initializerNode, Initializer initializer);
+ void endVisitInitializer(EclipseNode initializerNode, Initializer initializer);
+
+ /**
+ * Called for both methods (MethodDeclaration) and constructors (ConstructorDeclaration), but not for
+ * Clinit objects, which are a vestigial Eclipse thing that never contain anything. Static initializers
+ * show up as 'Initializer', in the visitInitializer method, with modifier bit STATIC set.
+ */
+ void visitMethod(EclipseNode methodNode, AbstractMethodDeclaration method);
+ void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation);
+ void endVisitMethod(EclipseNode methodNode, AbstractMethodDeclaration method);
+
+ /**
+ * Visits a method argument
+ */
+ void visitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method);
+ void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation);
+ void endVisitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method);
+
+ /**
+ * Visits a local declaration - that is, something like 'int x = 10;' on the method level.
+ */
+ void visitLocal(EclipseNode localNode, LocalDeclaration local);
+ void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation);
+ void endVisitLocal(EclipseNode localNode, LocalDeclaration local);
+
+ /**
+ * Visits a statement that isn't any of the other visit methods (e.g. TypeDeclaration).
+ */
+ void visitStatement(EclipseNode statementNode, Statement statement);
+ void endVisitStatement(EclipseNode statementNode, Statement statement);
+
+ /**
+ * Prints the structure of an AST.
+ */
+ public static class Printer implements EclipseASTVisitor {
+ private final PrintStream out;
+ private final boolean printContent;
+ private int disablePrinting = 0;
+ private int indent = 0;
+
+ /**
+ * @param printContent if true, bodies are printed directly, as java code,
+ * instead of a tree listing of every AST node inside it.
+ */
+ public Printer(boolean printContent) {
+ this(printContent, System.out);
+ }
+
+ /**
+ * @param printContent if true, bodies are printed directly, as java code,
+ * instead of a tree listing of every AST node inside it.
+ * @param out write output to this stream. You must close it yourself. flush() is called after every line.
+ *
+ * @see java.io.PrintStream#flush()
+ */
+ public Printer(boolean printContent, PrintStream out) {
+ this.printContent = printContent;
+ this.out = out;
+ }
+
+ private void forcePrint(String text, Object... params) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) sb.append(" ");
+ out.printf(sb.append(text).append('\n').toString(), params);
+ out.flush();
+ }
+
+ private void print(String text, Object... params) {
+ if (disablePrinting == 0) forcePrint(text, params);
+ }
+
+ private String str(char[] c) {
+ if (c == null) return "(NULL)";
+ return new String(c);
+ }
+
+ private String str(TypeReference type) {
+ if (type == null) return "(NULL)";
+ char[][] c = type.getTypeName();
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (char[] d : c) {
+ sb.append(first ? "" : ".").append(new String(d));
+ first = false;
+ }
+ return sb.toString();
+ }
+
+ public void visitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) {
+ out.println("---------------------------------------------------------");
+ out.println(node.isCompleteParse() ? "COMPLETE" : "incomplete");
+
+ print("<CUD %s%s>", node.getFileName(), Eclipse.isGenerated(unit) ? " (GENERATED)" : "");
+ indent++;
+ }
+
+ public void endVisitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) {
+ indent--;
+ print("</CUD>");
+ }
+
+ public void visitType(EclipseNode node, TypeDeclaration type) {
+ print("<TYPE %s%s>", str(type.name), Eclipse.isGenerated(type) ? " (GENERATED)" : "");
+ indent++;
+ if (printContent) {
+ print("%s", type);
+ disablePrinting++;
+ }
+ }
+
+ public void visitAnnotationOnType(TypeDeclaration type, EclipseNode node, Annotation annotation) {
+ forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation);
+ }
+
+ public void endVisitType(EclipseNode node, TypeDeclaration type) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</TYPE %s>", str(type.name));
+ }
+
+ public void visitInitializer(EclipseNode node, Initializer initializer) {
+ Block block = initializer.block;
+ boolean s = (block != null && block.statements != null);
+ print("<%s INITIALIZER: %s%s>",
+ (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance",
+ s ? "filled" : "blank",
+ Eclipse.isGenerated(initializer) ? " (GENERATED)" : "");
+ indent++;
+ if (printContent) {
+ if (initializer.block != null) print("%s", initializer.block);
+ disablePrinting++;
+ }
+ }
+
+ public void endVisitInitializer(EclipseNode node, Initializer initializer) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</%s INITIALIZER>", (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance");
+ }
+
+ public void visitField(EclipseNode node, FieldDeclaration field) {
+ print("<FIELD%s %s %s = %s>", Eclipse.isGenerated(field) ? " (GENERATED)" : "",
+ str(field.type), str(field.name), field.initialization);
+ indent++;
+ if (printContent) {
+ if (field.initialization != null) print("%s", field.initialization);
+ disablePrinting++;
+ }
+ }
+
+ public void visitAnnotationOnField(FieldDeclaration field, EclipseNode node, Annotation annotation) {
+ forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation);
+ }
+
+ public void endVisitField(EclipseNode node, FieldDeclaration field) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</FIELD %s %s>", str(field.type), str(field.name));
+ }
+
+ public void visitMethod(EclipseNode node, AbstractMethodDeclaration method) {
+ String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD";
+ print("<%s %s: %s%s>", type, str(method.selector), method.statements != null ? "filled" : "blank",
+ Eclipse.isGenerated(method) ? " (GENERATED)" : "");
+ indent++;
+ if (printContent) {
+ if (method.statements != null) print("%s", method);
+ disablePrinting++;
+ }
+ }
+
+ public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode node, Annotation annotation) {
+ forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(method) ? " (GENERATED)" : "", annotation);
+ }
+
+ public void endVisitMethod(EclipseNode node, AbstractMethodDeclaration method) {
+ if (printContent) disablePrinting--;
+ String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD";
+ indent--;
+ print("</%s %s>", type, str(method.selector));
+ }
+
+ public void visitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) {
+ print("<METHODARG%s %s %s = %s>", Eclipse.isGenerated(arg) ? " (GENERATED)" : "", str(arg.type), str(arg.name), arg.initialization);
+ indent++;
+ }
+
+ public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode node, Annotation annotation) {
+ print("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation);
+ }
+
+ public void endVisitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) {
+ indent--;
+ print("</METHODARG %s %s>", str(arg.type), str(arg.name));
+ }
+
+ public void visitLocal(EclipseNode node, LocalDeclaration local) {
+ print("<LOCAL%s %s %s = %s>", Eclipse.isGenerated(local) ? " (GENERATED)" : "", str(local.type), str(local.name), local.initialization);
+ indent++;
+ }
+
+ public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode node, Annotation annotation) {
+ print("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation);
+ }
+
+ public void endVisitLocal(EclipseNode node, LocalDeclaration local) {
+ indent--;
+ print("</LOCAL %s %s>", str(local.type), str(local.name));
+ }
+
+ public void visitStatement(EclipseNode node, Statement statement) {
+ print("<%s%s>", statement.getClass(), Eclipse.isGenerated(statement) ? " (GENERATED)" : "");
+ indent++;
+ print("%s", statement);
+ }
+
+ public void endVisitStatement(EclipseNode node, Statement statement) {
+ indent--;
+ print("</%s>", statement.getClass());
+ }
+ }
+}
diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java
new file mode 100644
index 00000000..aaa57603
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import lombok.core.AnnotationValues;
+
+/**
+ * Implement this interface if you want to be triggered for a specific annotation.
+ *
+ * You MUST replace 'T' with a specific annotation type, such as:
+ *
+ * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>}
+ *
+ * Because this generics parameter is inspected to figure out which class you're interested in.
+ *
+ * You also need to register yourself via SPI discovery as being an implementation of {@code EclipseAnnotationHandler}.
+ */
+public interface EclipseAnnotationHandler<T extends java.lang.annotation.Annotation> {
+ /**
+ * Called when an annotation is found that is likely to match the annotation you're interested in.
+ *
+ * Be aware that you'll be called for ANY annotation node in the source that looks like a match. There is,
+ * for example, no guarantee that the annotation node belongs to a method, even if you set your
+ * TargetType in the annotation to methods only.
+ *
+ * @param annotation The actual annotation - use this object to retrieve the annotation parameters.
+ * @param ast The Eclipse AST node representing the annotation.
+ * @param annotationNode The Lombok AST wrapper around the 'ast' parameter. You can use this object
+ * to travel back up the chain (something javac AST can't do) to the parent of the annotation, as well
+ * as access useful methods such as generating warnings or errors focused on the annotation.
+ * @return {@code true} if you don't want to be called again about this annotation during this
+ * compile session (you've handled it), or {@code false} to indicate you aren't done yet.
+ */
+ boolean handle(AnnotationValues<T> annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseNode annotationNode);
+}
diff --git a/src/core/lombok/eclipse/EclipseNode.java b/src/core/lombok/eclipse/EclipseNode.java
new file mode 100644
index 00000000..668e6a6e
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseNode.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import java.util.List;
+
+import lombok.core.AST.Kind;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Clinit;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+
+/**
+ * Eclipse specific version of the LombokNode class.
+ */
+public class EclipseNode extends lombok.core.LombokNode<EclipseAST, EclipseNode, ASTNode> {
+ /** {@inheritDoc} */
+ EclipseNode(EclipseAST ast, ASTNode node, List<EclipseNode> children, Kind kind) {
+ super(ast, node, children, kind);
+ }
+
+ /**
+ * Visits this node and all child nodes depth-first, calling the provided visitor's visit methods.
+ */
+ public void traverse(EclipseASTVisitor visitor) {
+ switch (getKind()) {
+ case COMPILATION_UNIT:
+ visitor.visitCompilationUnit(this, (CompilationUnitDeclaration)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration)get());
+ break;
+ case TYPE:
+ visitor.visitType(this, (TypeDeclaration)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitType(this, (TypeDeclaration)get());
+ break;
+ case FIELD:
+ visitor.visitField(this, (FieldDeclaration)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitField(this, (FieldDeclaration)get());
+ break;
+ case INITIALIZER:
+ visitor.visitInitializer(this, (Initializer)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitInitializer(this, (Initializer)get());
+ break;
+ case METHOD:
+ if (get() instanceof Clinit) return;
+ visitor.visitMethod(this, (AbstractMethodDeclaration)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitMethod(this, (AbstractMethodDeclaration)get());
+ break;
+ case ARGUMENT:
+ AbstractMethodDeclaration method = (AbstractMethodDeclaration)up().get();
+ visitor.visitMethodArgument(this, (Argument)get(), method);
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitMethodArgument(this, (Argument)get(), method);
+ break;
+ case LOCAL:
+ visitor.visitLocal(this, (LocalDeclaration)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitLocal(this, (LocalDeclaration)get());
+ break;
+ case ANNOTATION:
+ switch (up().getKind()) {
+ case TYPE:
+ visitor.visitAnnotationOnType((TypeDeclaration)up().get(), this, (Annotation)get());
+ break;
+ case FIELD:
+ visitor.visitAnnotationOnField((FieldDeclaration)up().get(), this, (Annotation)get());
+ break;
+ case METHOD:
+ visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)up().get(), this, (Annotation)get());
+ break;
+ case ARGUMENT:
+ visitor.visitAnnotationOnMethodArgument(
+ (Argument)parent.get(),
+ (AbstractMethodDeclaration)parent.directUp().get(),
+ this, (Annotation)get());
+ break;
+ case LOCAL:
+ visitor.visitAnnotationOnLocal((LocalDeclaration)parent.get(), this, (Annotation)get());
+ break;
+ default:
+ throw new AssertionError("Annotion not expected as child of a " + up().getKind());
+ }
+ break;
+ case STATEMENT:
+ visitor.visitStatement(this, (Statement)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitStatement(this, (Statement)get());
+ break;
+ default:
+ throw new AssertionError("Unexpected kind during node traversal: " + getKind());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getName() {
+ final char[] n;
+ if (node instanceof TypeDeclaration) n = ((TypeDeclaration)node).name;
+ else if (node instanceof FieldDeclaration) n = ((FieldDeclaration)node).name;
+ else if (node instanceof AbstractMethodDeclaration) n = ((AbstractMethodDeclaration)node).selector;
+ else if (node instanceof LocalDeclaration) n = ((LocalDeclaration)node).name;
+ else n = null;
+
+ return n == null ? null : new String(n);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void addError(String message) {
+ this.addError(message, this.get().sourceStart, this.get().sourceEnd);
+ }
+
+ /** Generate a compiler error that shows the wavy underline from-to the stated character positions. */
+ public void addError(String message, int sourceStart, int sourceEnd) {
+ ast.addProblem(ast.new ParseProblem(false, message, sourceStart, sourceEnd));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void addWarning(String message) {
+ this.addWarning(message, this.get().sourceStart, this.get().sourceEnd);
+ }
+
+ /** Generate a compiler warning that shows the wavy underline from-to the stated character positions. */
+ public void addWarning(String message, int sourceStart, int sourceEnd) {
+ ast.addProblem(ast.new ParseProblem(true, message, sourceStart, sourceEnd));
+ }
+
+ /** {@inheritDoc} */
+ @Override protected boolean calculateIsStructurallySignificant() {
+ if (node instanceof TypeDeclaration) return true;
+ if (node instanceof AbstractMethodDeclaration) return true;
+ if (node instanceof FieldDeclaration) return true;
+ if (node instanceof LocalDeclaration) return true;
+ if (node instanceof CompilationUnitDeclaration) return true;
+ return false;
+ }
+
+ /**
+ * Convenient shortcut to the owning EclipseAST object's isCompleteParse method.
+ *
+ * @see EclipseAST#isCompleteParse()
+ */
+ public boolean isCompleteParse() {
+ return ast.isCompleteParse();
+ }
+}
diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java
new file mode 100644
index 00000000..36c41504
--- /dev/null
+++ b/src/core/lombok/eclipse/HandlerLibrary.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import static lombok.eclipse.Eclipse.toQualifiedName;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.Lombok;
+import lombok.core.AnnotationValues;
+import lombok.core.PrintAST;
+import lombok.core.SpiLoadUtil;
+import lombok.core.TypeLibrary;
+import lombok.core.TypeResolver;
+import lombok.core.AnnotationValues.AnnotationValueDecodeFail;
+
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+
+/**
+ * This class tracks 'handlers' and knows how to invoke them for any given AST node.
+ *
+ * This class can find the handlers (via SPI discovery) and will set up the given AST node, such as
+ * building an AnnotationValues instance.
+ */
+public class HandlerLibrary {
+ /**
+ * Creates a new HandlerLibrary. Errors will be reported to the Eclipse Error log.
+ * You probably want to use {@link #load()} instead.
+ */
+ public HandlerLibrary() {}
+
+ private TypeLibrary typeLibrary = new TypeLibrary();
+
+ private static class AnnotationHandlerContainer<T extends Annotation> {
+ private EclipseAnnotationHandler<T> handler;
+ private Class<T> annotationClass;
+
+ AnnotationHandlerContainer(EclipseAnnotationHandler<T> handler, Class<T> annotationClass) {
+ this.handler = handler;
+ this.annotationClass = annotationClass;
+ }
+
+ public boolean handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation,
+ final EclipseNode annotationNode) {
+ AnnotationValues<T> annValues = Eclipse.createAnnotation(annotationClass, annotationNode);
+ return handler.handle(annValues, annotation, annotationNode);
+ }
+ }
+
+ private Map<String, AnnotationHandlerContainer<?>> annotationHandlers =
+ new HashMap<String, AnnotationHandlerContainer<?>>();
+
+ private Collection<EclipseASTVisitor> visitorHandlers = new ArrayList<EclipseASTVisitor>();
+
+ private boolean skipPrintAST;
+
+ /**
+ * Creates a new HandlerLibrary. Errors will be reported to the Eclipse Error log.
+ * then uses SPI discovery to load all annotation and visitor based handlers so that future calls
+ * to the handle methods will defer to these handlers.
+ */
+ public static HandlerLibrary load() {
+ HandlerLibrary lib = new HandlerLibrary();
+
+ loadAnnotationHandlers(lib);
+ loadVisitorHandlers(lib);
+
+ return lib;
+ }
+
+ /** Uses SPI Discovery to find implementations of {@link EclipseAnnotationHandler}. */
+ @SuppressWarnings("unchecked") private static void loadAnnotationHandlers(HandlerLibrary lib) {
+ try {
+ for (EclipseAnnotationHandler<?> handler : SpiLoadUtil.findServices(EclipseAnnotationHandler.class)) {
+ try {
+ Class<? extends Annotation> annotationClass =
+ SpiLoadUtil.findAnnotationClass(handler.getClass(), EclipseAnnotationHandler.class);
+ AnnotationHandlerContainer<?> container = new AnnotationHandlerContainer(handler, annotationClass);
+ if (lib.annotationHandlers.put(container.annotationClass.getName(), container) != null) {
+ Eclipse.error(null, "Duplicate handlers for annotation type: " + container.annotationClass.getName());
+ }
+ lib.typeLibrary.addType(container.annotationClass.getName());
+ } catch (Throwable t) {
+ Eclipse.error(null, "Can't load Lombok annotation handler for Eclipse: ", t);
+ }
+ }
+ } catch (IOException e) {
+ Lombok.sneakyThrow(e);
+ }
+ }
+
+ /** Uses SPI Discovery to find implementations of {@link EclipseASTVisitor}. */
+ private static void loadVisitorHandlers(HandlerLibrary lib) {
+ try {
+ for (EclipseASTVisitor visitor : SpiLoadUtil.findServices(EclipseASTVisitor.class)) {
+ lib.visitorHandlers.add(visitor);
+ }
+ } catch (Throwable t) {
+ throw Lombok.sneakyThrow(t);
+ }
+ }
+
+ /**
+ * Handles the provided annotation node by first finding a qualifying instance of
+ * {@link EclipseAnnotationHandler} and if one exists, calling it with a freshly cooked up
+ * instance of {@link AnnotationValues}.
+ *
+ * Note that depending on the printASTOnly flag, the {@link lombok.core.PrintAST} annotation
+ * will either be silently skipped, or everything that isn't {@code PrintAST} will be skipped.
+ *
+ * The HandlerLibrary will attempt to guess if the given annotation node represents a lombok annotation.
+ * For example, if {@code lombok.*} is in the import list, then this method will guess that
+ * {@code Getter} refers to {@code lombok.Getter}, presuming that {@link lombok.eclipse.handlers.HandleGetter}
+ * has been loaded.
+ *
+ * @param ast The Compilation Unit that contains the Annotation AST Node.
+ * @param annotationNode The Lombok AST Node representing the Annotation AST Node.
+ * @param annotation 'node.get()' - convenience parameter.
+ */
+ public boolean handle(CompilationUnitDeclaration ast, EclipseNode annotationNode,
+ org.eclipse.jdt.internal.compiler.ast.Annotation annotation) {
+ String pkgName = annotationNode.getPackageDeclaration();
+ Collection<String> imports = annotationNode.getImportStatements();
+
+ TypeResolver resolver = new TypeResolver(typeLibrary, pkgName, imports);
+ TypeReference rawType = annotation.type;
+ if (rawType == null) return false;
+ boolean handled = false;
+ for (String fqn : resolver.findTypeMatches(annotationNode, toQualifiedName(annotation.type.getTypeName()))) {
+ boolean isPrintAST = fqn.equals(PrintAST.class.getName());
+ if (isPrintAST == skipPrintAST) continue;
+ AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
+
+ if (container == null) continue;
+
+ try {
+ handled |= container.handle(annotation, annotationNode);
+ } catch (AnnotationValueDecodeFail fail) {
+ fail.owner.setError(fail.getMessage(), fail.idx);
+ } catch (Throwable t) {
+ Eclipse.error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t);
+ }
+ }
+
+ return handled;
+ }
+
+ /**
+ * Will call all registered {@link EclipseASTVisitor} instances.
+ */
+ public void callASTVisitors(EclipseAST ast) {
+ for (EclipseASTVisitor visitor : visitorHandlers) try {
+ ast.traverse(visitor);
+ } catch (Throwable t) {
+ Eclipse.error((CompilationUnitDeclaration) ast.top().get(),
+ String.format("Lombok visitor handler %s failed", visitor.getClass()), t);
+ }
+ }
+
+ /**
+ * Lombok does not currently support triggering annotations in a specified order; the order is essentially
+ * random right now. This lack of order is particularly annoying for the {@code PrintAST} annotation,
+ * which is almost always intended to run last. Hence, this hack, which lets it in fact run last.
+ *
+ * @see #skipAllButPrintAST()
+ */
+ public void skipPrintAST() {
+ skipPrintAST = true;
+ }
+
+ /** @see #skipPrintAST() */
+ public void skipAllButPrintAST() {
+ skipPrintAST = false;
+ }
+}
diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java
new file mode 100644
index 00000000..3b5482ca
--- /dev/null
+++ b/src/core/lombok/eclipse/TransformEclipseAST.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse;
+
+import java.lang.reflect.Field;
+
+import lombok.patcher.Symbols;
+
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
+
+/**
+ * Entry point for the Eclipse Parser patch that lets lombok modify the Abstract Syntax Tree as generated by
+ * Eclipse's parser implementations. This class is injected into the appropriate OSGi ClassLoader and can thus
+ * use any classes that belong to org.eclipse.jdt.(apt.)core.
+ *
+ * Note that, for any Method body, if Bit24 is set, the Eclipse parser has been patched to never attempt to
+ * (re)parse it. You should set Bit24 on any MethodDeclaration object you inject into the AST:
+ *
+ * {@code methodDeclaration.bits |= ASTNode.Bit24; //0x800000}
+ *
+ * @author rzwitserloot
+ * @author rspilker
+ */
+public class TransformEclipseAST {
+ private final EclipseAST ast;
+ //The patcher hacks this field onto CUD. It's public.
+ private static final Field astCacheField;
+ private static final HandlerLibrary handlers;
+
+ private static boolean disableLombok = false;
+
+ static {
+ Field f = null;
+ HandlerLibrary l = null;
+ try {
+ l = HandlerLibrary.load();
+ f = CompilationUnitDeclaration.class.getDeclaredField("$lombokAST");
+ } catch (Throwable t) {
+ try {
+ Eclipse.error(null, "Problem initializing lombok", t);
+ } catch (Throwable t2) {
+ System.err.println("Problem initializing lombok");
+ t.printStackTrace();
+ }
+ disableLombok = true;
+ }
+ astCacheField = f;
+ handlers = l;
+ }
+
+ public static void transform_swapped(CompilationUnitDeclaration ast, Parser parser) {
+ transform(parser, ast);
+ }
+
+ /**
+ * This method is called immediately after Eclipse finishes building a CompilationUnitDeclaration, which is
+ * the top-level AST node when Eclipse parses a source file. The signature is 'magic' - you should not
+ * change it!
+ *
+ * Eclipse's parsers often operate in diet mode, which means many parts of the AST have been left blank.
+ * Be ready to deal with just about anything being null, such as the Statement[] arrays of the Method AST nodes.
+ *
+ * @param parser The Eclipse parser object that generated the AST.
+ * @param ast The AST node belonging to the compilation unit (java speak for a single source file).
+ */
+ public static void transform(Parser parser, CompilationUnitDeclaration ast) {
+ if (disableLombok) return;
+
+ if (Symbols.hasSymbol("lombok.disable")) return;
+
+ try {
+ EclipseAST existing = getCache(ast);
+ if (existing == null) {
+ existing = new EclipseAST(ast);
+ setCache(ast, existing);
+ } else existing.reparse();
+ new TransformEclipseAST(existing).go();
+ } catch (Throwable t) {
+ try {
+ String message = "Lombok can't parse this source: " + t.toString();
+
+ EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0);
+ t.printStackTrace();
+ } catch (Throwable t2) {
+ try {
+ Eclipse.error(ast, "Can't create an error in the problems dialog while adding: " + t.toString(), t2);
+ } catch (Throwable t3) {
+ //This seems risky to just silently turn off lombok, but if we get this far, something pretty
+ //drastic went wrong. For example, the eclipse help system's JSP compiler will trigger a lombok call,
+ //but due to class loader shenanigans we'll actually get here due to a cascade of
+ //ClassNotFoundErrors. This is the right action for the help system (no lombok needed for that JSP compiler,
+ //of course). 'disableLombok' is static, but each context classloader (e.g. each eclipse OSGi plugin) has
+ //it's own edition of this class, so this won't turn off lombok everywhere.
+ disableLombok = true;
+ }
+ }
+ }
+ }
+
+ private static EclipseAST getCache(CompilationUnitDeclaration ast) {
+ if (astCacheField == null) return null;
+ try {
+ return (EclipseAST)astCacheField.get(ast);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static void setCache(CompilationUnitDeclaration ast, EclipseAST cache) {
+ if (astCacheField != null) try {
+ astCacheField.set(ast, cache);
+ } catch (Exception ignore) {
+ ignore.printStackTrace();
+ }
+ }
+
+ public TransformEclipseAST(EclipseAST ast) {
+ this.ast = ast;
+ }
+
+ /**
+ * First handles all lombok annotations except PrintAST, then calls all non-annotation based handlers.
+ * then handles any PrintASTs.
+ */
+ public void go() {
+ handlers.skipPrintAST();
+ ast.traverse(new AnnotationVisitor());
+ handlers.callASTVisitors(ast);
+ handlers.skipAllButPrintAST();
+ ast.traverse(new AnnotationVisitor());
+ }
+
+ private static class AnnotationVisitor extends EclipseASTAdapter {
+ @Override public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) {
+ if (annotationNode.isHandled()) return;
+ CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
+ boolean handled = handlers.handle(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {
+ if (annotationNode.isHandled()) return;
+ CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
+ boolean handled = handlers.handle(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) {
+ if (annotationNode.isHandled()) return;
+ CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
+ boolean handled = handlers.handle(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {
+ if (annotationNode.isHandled()) return;
+ CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
+ boolean handled = handlers.handle(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) {
+ if (annotationNode.isHandled()) return;
+ CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
+ boolean handled = handlers.handle(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
new file mode 100644
index 00000000..2f676d09
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.fromQualifiedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import lombok.AccessLevel;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+
+/**
+ * Container for static utility methods useful to handlers written for eclipse.
+ */
+public class EclipseHandlerUtil {
+ private EclipseHandlerUtil() {
+ //Prevent instantiation
+ }
+
+ /**
+ * Checks if the given type reference represents a primitive type.
+ */
+ public static boolean isPrimitive(TypeReference ref) {
+ if (ref.dimensions() > 0) return false;
+ return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(Eclipse.toQualifiedName(ref.getTypeName())).matches();
+ }
+
+ /**
+ * Turns an {@code AccessLevel} instance into the flag bit used by eclipse.
+ */
+ public static int toEclipseModifier(AccessLevel value) {
+ switch (value) {
+ case MODULE:
+ case PACKAGE:
+ return 0;
+ default:
+ case PUBLIC:
+ return ClassFileConstants.AccPublic;
+ case PROTECTED:
+ return ClassFileConstants.AccProtected;
+ case PRIVATE:
+ return ClassFileConstants.AccPrivate;
+ }
+ }
+
+ /**
+ * Checks if an eclipse-style array-of-array-of-characters to represent a fully qualified name ('foo.bar.baz'), matches a plain
+ * string containing the same fully qualified name with dots in the string.
+ */
+ public static boolean nameEquals(char[][] typeName, String string) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (char[] elem : typeName) {
+ if (first) first = false;
+ else sb.append('.');
+ sb.append(elem);
+ }
+
+ return string.contentEquals(sb);
+ }
+
+ /** Serves as return value for the methods that check for the existence of fields and methods. */
+ public enum MemberExistsResult {
+ NOT_EXISTS, EXISTS_BY_USER, EXISTS_BY_LOMBOK;
+ }
+
+ /**
+ * Checks if there is a field with the provided name.
+ *
+ * @param fieldName the field name to check for.
+ * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult fieldExists(String fieldName, EclipseNode node) {
+ while (node != null && !(node.get() instanceof TypeDeclaration)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof TypeDeclaration) {
+ TypeDeclaration typeDecl = (TypeDeclaration)node.get();
+ if (typeDecl.fields != null) for (FieldDeclaration def : typeDecl.fields) {
+ char[] fName = def.name;
+ if (fName == null) continue;
+ if (fieldName.equals(new String(fName))) {
+ EclipseNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Checks if there is a method with the provided name. In case of multiple methods (overloading), only
+ * the first method decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
+ *
+ * @param methodName the method name to check for.
+ * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult methodExists(String methodName, EclipseNode node) {
+ while (node != null && !(node.get() instanceof TypeDeclaration)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof TypeDeclaration) {
+ TypeDeclaration typeDecl = (TypeDeclaration)node.get();
+ if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) {
+ char[] mName = def.selector;
+ if (mName == null) continue;
+ if (methodName.equals(new String(mName))) {
+ EclipseNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only
+ * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
+ *
+ * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult constructorExists(EclipseNode node) {
+ while (node != null && !(node.get() instanceof TypeDeclaration)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof TypeDeclaration) {
+ TypeDeclaration typeDecl = (TypeDeclaration)node.get();
+ if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) {
+ if (def instanceof ConstructorDeclaration) {
+ if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue;
+ EclipseNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Returns the constructor that's already been generated by lombok.
+ * Provide any node that represents the type (TypeDeclaration) to look in, or any child node thereof.
+ */
+ public static EclipseNode getExistingLombokConstructor(EclipseNode node) {
+ while (node != null && !(node.get() instanceof TypeDeclaration)) {
+ node = node.up();
+ }
+
+ if (node == null) return null;
+
+ if (node.get() instanceof TypeDeclaration) {
+ for (AbstractMethodDeclaration def : ((TypeDeclaration)node.get()).methods) {
+ if (def instanceof ConstructorDeclaration) {
+ if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue;
+ EclipseNode existing = node.getNodeFor(def);
+ if (existing.isHandled()) return existing;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the method that's already been generated by lombok with the given name.
+ * Provide any node that represents the type (TypeDeclaration) to look in, or any child node thereof.
+ */
+ public static EclipseNode getExistingLombokMethod(String methodName, EclipseNode node) {
+ while (node != null && !(node.get() instanceof TypeDeclaration)) {
+ node = node.up();
+ }
+
+ if (node == null) return null;
+
+ if (node.get() instanceof TypeDeclaration) {
+ for (AbstractMethodDeclaration def : ((TypeDeclaration)node.get()).methods) {
+ char[] mName = def.selector;
+ if (mName == null) continue;
+ if (methodName.equals(new String(mName))) {
+ EclipseNode existing = node.getNodeFor(def);
+ if (existing.isHandled()) return existing;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
+ */
+ public static void injectField(EclipseNode type, FieldDeclaration field) {
+ TypeDeclaration parent = (TypeDeclaration) type.get();
+
+ if (parent.fields == null) {
+ parent.fields = new FieldDeclaration[1];
+ parent.fields[0] = field;
+ } else {
+ FieldDeclaration[] newArray = new FieldDeclaration[parent.fields.length + 1];
+ System.arraycopy(parent.fields, 0, newArray, 0, parent.fields.length);
+ newArray[parent.fields.length] = field;
+ parent.fields = newArray;
+ }
+
+ type.add(field, Kind.FIELD).recursiveSetHandled();
+ }
+
+ /**
+ * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}.
+ */
+ public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) {
+ TypeDeclaration parent = (TypeDeclaration) type.get();
+
+ if (parent.methods == null) {
+ parent.methods = new AbstractMethodDeclaration[1];
+ parent.methods[0] = method;
+ } else {
+ boolean injectionComplete = false;
+ if (method instanceof ConstructorDeclaration) {
+ for (int i = 0 ; i < parent.methods.length ; i++) {
+ if (parent.methods[i] instanceof ConstructorDeclaration &&
+ (parent.methods[i].bits & ASTNode.IsDefaultConstructor) != 0) {
+ EclipseNode tossMe = type.getNodeFor(parent.methods[i]);
+ parent.methods[i] = method;
+ if (tossMe != null) tossMe.up().removeChild(tossMe);
+ injectionComplete = true;
+ break;
+ }
+ }
+ }
+ if (!injectionComplete) {
+ AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[parent.methods.length + 1];
+ System.arraycopy(parent.methods, 0, newArray, 0, parent.methods.length);
+ newArray[parent.methods.length] = method;
+ parent.methods = newArray;
+ }
+ }
+
+ type.add(method, Kind.METHOD).recursiveSetHandled();
+ }
+
+ /**
+ * Searches the given field node for annotations and returns each one that matches the provided regular expression pattern.
+ *
+ * Only the simple name is checked - the package and any containing class are ignored.
+ */
+ public static Annotation[] findAnnotations(FieldDeclaration field, Pattern namePattern) {
+ List<Annotation> result = new ArrayList<Annotation>();
+ if (field.annotations == null) return new Annotation[0];
+ for (Annotation annotation : field.annotations) {
+ TypeReference typeRef = annotation.type;
+ if (typeRef != null && typeRef.getTypeName()!= null) {
+ char[][] typeName = typeRef.getTypeName();
+ String suspect = new String(typeName[typeName.length - 1]);
+ if (namePattern.matcher(suspect).matches()) {
+ result.add(annotation);
+ }
+ }
+ }
+ return result.toArray(new Annotation[0]);
+ }
+
+ /**
+ * Generates a new statement that checks if the given variable is null, and if so, throws a {@code NullPointerException} with the
+ * variable name as message.
+ */
+ public static Statement generateNullCheck(AbstractVariableDeclaration variable, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+
+ if (isPrimitive(variable.type)) return null;
+ AllocationExpression exception = new AllocationExpression();
+ Eclipse.setGeneratedBy(exception, source);
+ exception.type = new QualifiedTypeReference(fromQualifiedName("java.lang.NullPointerException"), new long[]{p, p, p});
+ Eclipse.setGeneratedBy(exception.type, source);
+ exception.arguments = new Expression[] { new StringLiteral(variable.name, pS, pE, 0)};
+ Eclipse.setGeneratedBy(exception.arguments[0], source);
+ ThrowStatement throwStatement = new ThrowStatement(exception, pS, pE);
+ Eclipse.setGeneratedBy(throwStatement, source);
+
+ SingleNameReference varName = new SingleNameReference(variable.name, p);
+ Eclipse.setGeneratedBy(varName, source);
+ NullLiteral nullLiteral = new NullLiteral(pS, pE);
+ Eclipse.setGeneratedBy(nullLiteral, source);
+ EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL);
+ equalExpression.sourceStart = pS; equalExpression.sourceEnd = pE;
+ Eclipse.setGeneratedBy(equalExpression, source);
+ IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0);
+ Eclipse.setGeneratedBy(ifStatement, source);
+ return ifStatement;
+ }
+
+ /**
+ * Create an annotation of the given name, and is marked as being generated by the given source.
+ */
+ public static MarkerAnnotation makeMarkerAnnotation(char[][] name, ASTNode source) {
+ long pos = (long)source.sourceStart << 32 | source.sourceEnd;
+ TypeReference typeRef = new QualifiedTypeReference(name, new long[] {pos, pos, pos});
+ Eclipse.setGeneratedBy(typeRef, source);
+ MarkerAnnotation ann = new MarkerAnnotation(typeRef, (int)(pos >> 32));
+ ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = (int)pos;
+ Eclipse.setGeneratedBy(ann, source);
+ return ann;
+ }
+
+ /**
+ * Given a list of field names and a node referring to a type, finds each name in the list that does not match a field within the type.
+ */
+ public static List<Integer> createListOfNonExistentFields(List<String> list, EclipseNode type, boolean excludeStandard, boolean excludeTransient) {
+ boolean[] matched = new boolean[list.size()];
+
+ for (EclipseNode child : type.down()) {
+ if (list.isEmpty()) break;
+ if (child.getKind() != Kind.FIELD) continue;
+ if (excludeStandard) {
+ if ((((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccStatic) != 0) continue;
+ if (child.getName().startsWith("$")) continue;
+ }
+ if (excludeTransient && (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccTransient) != 0) continue;
+ int idx = list.indexOf(child.getName());
+ if (idx > -1) matched[idx] = true;
+ }
+
+ List<Integer> problematic = new ArrayList<Integer>();
+ for (int i = 0 ; i < list.size() ; i++) {
+ if (!matched[i]) problematic.add(i);
+ }
+
+ return problematic;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleCleanup.java b/src/core/lombok/eclipse/handlers/HandleCleanup.java
new file mode 100644
index 00000000..d296e96b
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleCleanup.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import java.util.Arrays;
+
+import lombok.Cleanup;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
+import org.eclipse.jdt.internal.compiler.ast.CastExpression;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.Cleanup} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> {
+ public boolean handle(AnnotationValues<Cleanup> annotation, Annotation ast, EclipseNode annotationNode) {
+ String cleanupName = annotation.getInstance().value();
+ if (cleanupName.length() == 0) {
+ annotationNode.addError("cleanupName cannot be the empty string.");
+ return true;
+ }
+
+ if (annotationNode.up().getKind() != Kind.LOCAL) {
+ annotationNode.addError("@Cleanup is legal only on local variable declarations.");
+ return true;
+ }
+
+ LocalDeclaration decl = (LocalDeclaration)annotationNode.up().get();
+
+ if (decl.initialization == null) {
+ annotationNode.addError("@Cleanup variable declarations need to be initialized.");
+ return true;
+ }
+
+ EclipseNode ancestor = annotationNode.up().directUp();
+ ASTNode blockNode = ancestor.get();
+
+ final boolean isSwitch;
+ final Statement[] statements;
+ if (blockNode instanceof AbstractMethodDeclaration) {
+ isSwitch = false;
+ statements = ((AbstractMethodDeclaration)blockNode).statements;
+ } else if (blockNode instanceof Block) {
+ isSwitch = false;
+ statements = ((Block)blockNode).statements;
+ } else if (blockNode instanceof SwitchStatement) {
+ isSwitch = true;
+ statements = ((SwitchStatement)blockNode).statements;
+ } else {
+ annotationNode.addError("@Cleanup is legal only on a local variable declaration inside a block.");
+ return true;
+ }
+
+ if (statements == null) {
+ annotationNode.addError("LOMBOK BUG: Parent block does not contain any statements.");
+ return true;
+ }
+
+ int start = 0;
+ for (; start < statements.length ; start++) {
+ if (statements[start] == decl) break;
+ }
+
+ if (start == statements.length) {
+ annotationNode.addError("LOMBOK BUG: Can't find this local variable declaration inside its parent.");
+ return true;
+ }
+
+ start++; //We start with try{} *AFTER* the var declaration.
+
+ int end;
+ if (isSwitch) {
+ end = start + 1;
+ for (; end < statements.length ; end++) {
+ if (statements[end] instanceof CaseStatement) {
+ break;
+ }
+ }
+ } else end = statements.length;
+
+ //At this point:
+ // start-1 = Local Declaration marked with @Cleanup
+ // start = first instruction that needs to be wrapped into a try block
+ // end = last intruction of the scope -OR- last instruction before the next case label in switch statements.
+ // hence:
+ // [start, end) = statements for the try block.
+
+ Statement[] tryBlock = new Statement[end - start];
+ System.arraycopy(statements, start, tryBlock, 0, end-start);
+ //Remove the stuff we just dumped into the tryBlock, and then leave room for the try node.
+ int newStatementsLength = statements.length - (end-start); //Remove room for every statement moved into try block...
+ newStatementsLength += 1; //But add room for the TryStatement node itself.
+ Statement[] newStatements = new Statement[newStatementsLength];
+ System.arraycopy(statements, 0, newStatements, 0, start); //copy all statements before the try block verbatim.
+ System.arraycopy(statements, end, newStatements, start+1, statements.length - end); //For switch statements.
+
+ doAssignmentCheck(annotationNode, tryBlock, decl.name);
+
+ TryStatement tryStatement = new TryStatement();
+ Eclipse.setGeneratedBy(tryStatement, ast);
+ tryStatement.tryBlock = new Block(0);
+ tryStatement.tryBlock.statements = tryBlock;
+ newStatements[start] = tryStatement;
+
+ Statement[] finallyBlock = new Statement[1];
+ MessageSend unsafeClose = new MessageSend();
+ Eclipse.setGeneratedBy(unsafeClose, ast);
+ unsafeClose.sourceStart = ast.sourceStart;
+ unsafeClose.sourceEnd = ast.sourceEnd;
+ SingleNameReference receiver = new SingleNameReference(decl.name, 0);
+ Eclipse.setGeneratedBy(receiver, ast);
+ unsafeClose.receiver = receiver;
+ long nameSourcePosition = (long)ast.sourceStart << 32 | ast.sourceEnd;
+ if (ast.memberValuePairs() != null) for (MemberValuePair pair : ast.memberValuePairs()) {
+ if (pair.name != null && new String(pair.name).equals("value")) {
+ nameSourcePosition = (long)pair.value.sourceStart << 32 | pair.value.sourceEnd;
+ break;
+ }
+ }
+ unsafeClose.nameSourcePosition = nameSourcePosition;
+ unsafeClose.selector = cleanupName.toCharArray();
+ finallyBlock[0] = unsafeClose;
+ tryStatement.finallyBlock = new Block(0);
+ Eclipse.setGeneratedBy(tryStatement.finallyBlock, ast);
+ tryStatement.finallyBlock.statements = finallyBlock;
+
+ tryStatement.catchArguments = null;
+ tryStatement.catchBlocks = null;
+
+ if (blockNode instanceof AbstractMethodDeclaration) {
+ ((AbstractMethodDeclaration)blockNode).statements = newStatements;
+ } else if (blockNode instanceof Block) {
+ ((Block)blockNode).statements = newStatements;
+ } else if (blockNode instanceof SwitchStatement) {
+ ((SwitchStatement)blockNode).statements = newStatements;
+ }
+
+ ancestor.rebuild();
+
+ return true;
+ }
+
+ private void doAssignmentCheck(EclipseNode node, Statement[] tryBlock, char[] varName) {
+ for (Statement statement : tryBlock) doAssignmentCheck0(node, statement, varName);
+ }
+
+ private void doAssignmentCheck0(EclipseNode node, Statement statement, char[] varName) {
+ if (statement instanceof Assignment)
+ doAssignmentCheck0(node, ((Assignment)statement).expression, varName);
+ else if (statement instanceof LocalDeclaration)
+ doAssignmentCheck0(node, ((LocalDeclaration)statement).initialization, varName);
+ else if (statement instanceof CastExpression)
+ doAssignmentCheck0(node, ((CastExpression)statement).expression, varName);
+ else if (statement instanceof SingleNameReference) {
+ if (Arrays.equals(((SingleNameReference)statement).token, varName)) {
+ EclipseNode problemNode = node.getNodeFor(statement);
+ if (problemNode != null) problemNode.addWarning(
+ "You're assigning an auto-cleanup variable to something else. This is a bad idea.");
+ }
+ }
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java
new file mode 100644
index 00000000..8c4e07ce
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleData.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.*;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.Data} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleData implements EclipseAnnotationHandler<Data> {
+ public boolean handle(AnnotationValues<Data> annotation, Annotation ast, EclipseNode annotationNode) {
+ Data ann = annotation.getInstance();
+ EclipseNode typeNode = annotationNode.up();
+
+ TypeDeclaration typeDecl = null;
+ if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get();
+ int modifiers = typeDecl == null ? 0 : typeDecl.modifiers;
+ boolean notAClass = (modifiers &
+ (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0;
+
+ if (typeDecl == null || notAClass) {
+ annotationNode.addError("@Data is only supported on a class.");
+ return false;
+ }
+
+ List<EclipseNode> nodesForConstructor = new ArrayList<EclipseNode>();
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ //Skip fields that start with $
+ if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue;
+ //Skip static fields.
+ if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue;
+ boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0;
+ boolean isNonNull = findAnnotations(fieldDecl, TransformationsUtil.NON_NULL_PATTERN).length != 0;
+ if ((isFinal || isNonNull) && fieldDecl.initialization == null) nodesForConstructor.add(child);
+ new HandleGetter().generateGetterForField(child, annotationNode.get());
+ if (!isFinal) new HandleSetter().generateSetterForField(child, annotationNode.get());
+ }
+
+ new HandleToString().generateToStringForType(typeNode, annotationNode);
+ new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
+
+ //Careful: Generate the public static constructor (if there is one) LAST, so that any attempt to
+ //'find callers' on the annotation node will find callers of the constructor, which is by far the
+ //most useful of the many methods built by @Data. This trick won't work for the non-static constructor,
+ //for whatever reason, though you can find callers of that one by focusing on the class name itself
+ //and hitting 'find callers'.
+
+ if (constructorExists(typeNode) == MemberExistsResult.NOT_EXISTS) {
+ ConstructorDeclaration constructor = createConstructor(
+ ann.staticConstructor().length() == 0, typeNode, nodesForConstructor, ast);
+ injectMethod(typeNode, constructor);
+ }
+
+ if (ann.staticConstructor().length() > 0) {
+ if (methodExists("of", typeNode) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration staticConstructor = createStaticConstructor(
+ ann.staticConstructor(), typeNode, nodesForConstructor, ast);
+ injectMethod(typeNode, staticConstructor);
+ }
+ }
+
+ return false;
+ }
+
+ private ConstructorDeclaration createConstructor(boolean isPublic,
+ EclipseNode type, Collection<EclipseNode> fields, ASTNode source) {
+ long p = (long)source.sourceStart << 32 | source.sourceEnd;
+
+ ConstructorDeclaration constructor = new ConstructorDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ Eclipse.setGeneratedBy(constructor, source);
+
+ constructor.modifiers = EclipseHandlerUtil.toEclipseModifier(isPublic ? AccessLevel.PUBLIC : AccessLevel.PRIVATE);
+ constructor.annotations = null;
+ constructor.selector = ((TypeDeclaration)type.get()).name;
+ constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
+ Eclipse.setGeneratedBy(constructor.constructorCall, source);
+ constructor.thrownExceptions = null;
+ constructor.typeParameters = null;
+ constructor.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart;
+ constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd;
+ constructor.arguments = null;
+
+ List<Argument> args = new ArrayList<Argument>();
+ List<Statement> assigns = new ArrayList<Statement>();
+ List<Statement> nullChecks = new ArrayList<Statement>();
+
+ for (EclipseNode fieldNode : fields) {
+ FieldDeclaration field = (FieldDeclaration) fieldNode.get();
+ FieldReference thisX = new FieldReference(("this." + new String(field.name)).toCharArray(), p);
+ Eclipse.setGeneratedBy(thisX, source);
+ thisX.receiver = new ThisReference((int)(p >> 32), (int)p);
+ Eclipse.setGeneratedBy(thisX.receiver, source);
+ thisX.token = field.name;
+
+ SingleNameReference assignmentNameRef = new SingleNameReference(field.name, p);
+ Eclipse.setGeneratedBy(assignmentNameRef, source);
+ Assignment assignment = new Assignment(thisX, assignmentNameRef, (int)p);
+ Eclipse.setGeneratedBy(assignment, source);
+ assigns.add(assignment);
+ long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd;
+ Argument argument = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL);
+ Eclipse.setGeneratedBy(argument, source);
+ Annotation[] nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
+ Annotation[] nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);
+ if (nonNulls.length != 0) {
+ Statement nullCheck = generateNullCheck(field, source);
+ if (nullCheck != null) nullChecks.add(nullCheck);
+ }
+ Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables, source);
+ if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations;
+ args.add(argument);
+ }
+
+ nullChecks.addAll(assigns);
+ constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]);
+ constructor.arguments = args.isEmpty() ? null : args.toArray(new Argument[args.size()]);
+ return constructor;
+ }
+
+ private MethodDeclaration createStaticConstructor(String name, EclipseNode type, Collection<EclipseNode> fields, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+
+ MethodDeclaration constructor = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ Eclipse.setGeneratedBy(constructor, source);
+
+ constructor.modifiers = EclipseHandlerUtil.toEclipseModifier(AccessLevel.PUBLIC) | Modifier.STATIC;
+ TypeDeclaration typeDecl = (TypeDeclaration) type.get();
+ if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) {
+ TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length];
+ int idx = 0;
+ for (TypeParameter param : typeDecl.typeParameters) {
+ TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd);
+ Eclipse.setGeneratedBy(typeRef, source);
+ refs[idx++] = typeRef;
+ }
+ constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p);
+ } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p);
+ Eclipse.setGeneratedBy(constructor.returnType, source);
+ constructor.annotations = null;
+ constructor.selector = name.toCharArray();
+ constructor.thrownExceptions = null;
+ constructor.typeParameters = copyTypeParams(((TypeDeclaration)type.get()).typeParameters, source);
+ constructor.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart;
+ constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd;
+
+ List<Argument> args = new ArrayList<Argument>();
+ List<Expression> assigns = new ArrayList<Expression>();
+ AllocationExpression statement = new AllocationExpression();
+ statement.sourceStart = pS; statement.sourceEnd = pE;
+ Eclipse.setGeneratedBy(statement, source);
+ statement.type = copyType(constructor.returnType, source);
+
+ for (EclipseNode fieldNode : fields) {
+ FieldDeclaration field = (FieldDeclaration) fieldNode.get();
+ long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd;
+ SingleNameReference nameRef = new SingleNameReference(field.name, fieldPos);
+ Eclipse.setGeneratedBy(nameRef, source);
+ assigns.add(nameRef);
+
+ Argument argument = new Argument(field.name, fieldPos, copyType(field.type, source), 0);
+ Eclipse.setGeneratedBy(argument, source);
+
+ Annotation[] copiedAnnotations = copyAnnotations(
+ findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN),
+ findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN), source);
+ if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations;
+ args.add(new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL));
+ }
+
+ statement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]);
+ constructor.arguments = args.isEmpty() ? null : args.toArray(new Argument[args.size()]);
+ constructor.statements = new Statement[] { new ReturnStatement(statement, (int)(p >> 32), (int)p) };
+ Eclipse.setGeneratedBy(constructor.statements[0], source);
+ return constructor;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
new file mode 100644
index 00000000..7c0980c8
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
@@ -0,0 +1,718 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import static lombok.eclipse.Eclipse.copyTypes;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.CastExpression;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
+import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Reference;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.SuperReference;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.Wildcard;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.mangosdk.spi.ProviderFor;
+
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+/**
+ * Handles the {@code EqualsAndHashCode} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsAndHashCode> {
+ private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ "byte", "short", "int", "long", "char", "boolean", "double", "float")));
+
+ private void checkForBogusFieldNames(EclipseNode type, AnnotationValues<EqualsAndHashCode> annotation) {
+ if (annotation.isExplicit("exclude")) {
+ for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, true)) {
+ annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
+ }
+ }
+ if (annotation.isExplicit("of")) {
+ for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false)) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
+ }
+
+ public void generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode errorNode) {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Eclipse.annotationTypeMatches(EqualsAndHashCode.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ generateMethods(typeNode, errorNode, null, null, null, false);
+ }
+
+ @Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation,
+ Annotation ast, EclipseNode annotationNode) {
+ EqualsAndHashCode ann = annotation.getInstance();
+ List<String> excludes = Arrays.asList(ann.exclude());
+ List<String> includes = Arrays.asList(ann.of());
+ EclipseNode typeNode = annotationNode.up();
+
+ checkForBogusFieldNames(typeNode, annotation);
+
+ Boolean callSuper = ann.callSuper();
+ if (!annotation.isExplicit("callSuper")) callSuper = null;
+ if (!annotation.isExplicit("exclude")) excludes = null;
+ if (!annotation.isExplicit("of")) includes = null;
+
+ if (excludes != null && includes != null) {
+ excludes = null;
+ annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored.");
+ }
+
+ return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true);
+ }
+
+ public boolean generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes,
+ Boolean callSuper, boolean whineIfExists) {
+ assert excludes == null || includes == null;
+
+ TypeDeclaration typeDecl = null;
+
+ if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get();
+ int modifiers = typeDecl == null ? 0 : typeDecl.modifiers;
+ boolean notAClass = (modifiers &
+ (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0;
+
+ if (typeDecl == null || notAClass) {
+ errorNode.addError("@EqualsAndHashCode is only supported on a class.");
+ return false;
+ }
+
+ boolean implicitCallSuper = callSuper == null;
+
+ if (callSuper == null) {
+ try {
+ callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {
+ throw new InternalError("Lombok bug - this cannot happen - can't find callSuper field in EqualsAndHashCode annotation.");
+ }
+ }
+
+ boolean isDirectDescendantOfObject = true;
+
+ if (typeDecl.superclass != null) {
+ String p = typeDecl.superclass.toString();
+ isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object");
+ }
+
+ if (isDirectDescendantOfObject && callSuper) {
+ errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless.");
+ return true;
+ }
+
+ if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) {
+ errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.");
+ }
+
+ List<EclipseNode> nodesForEquality = new ArrayList<EclipseNode>();
+ if (includes != null) {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ if (includes.contains(new String(fieldDecl.name))) nodesForEquality.add(child);
+ }
+ } else {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ //Skip static fields.
+ if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue;
+ //Skip transient fields.
+ if ((fieldDecl.modifiers & ClassFileConstants.AccTransient) != 0) continue;
+ //Skip excluded fields.
+ if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue;
+ //Skip fields that start with $.
+ if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue;
+ nodesForEquality.add(child);
+ }
+ }
+
+ switch (methodExists("hashCode", typeNode)) {
+ case NOT_EXISTS:
+ MethodDeclaration hashCode = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get());
+ injectMethod(typeNode, hashCode);
+ break;
+ case EXISTS_BY_LOMBOK:
+ break;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating hashCode(): A method with that name already exists");
+ }
+ break;
+ }
+
+ switch (methodExists("equals", typeNode)) {
+ case NOT_EXISTS:
+ MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get());
+ injectMethod(typeNode, equals);
+ break;
+ case EXISTS_BY_LOMBOK:
+ break;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating equals(Object other): A method with that name already exists");
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ private MethodDeclaration createHashCode(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+
+ MethodDeclaration method = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ Eclipse.setGeneratedBy(method, source);
+
+ method.modifiers = EclipseHandlerUtil.toEclipseModifier(AccessLevel.PUBLIC);
+ method.returnType = TypeReference.baseTypeReference(TypeIds.T_int, 0);
+ Eclipse.setGeneratedBy(method.returnType, source);
+ method.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)};
+ method.selector = "hashCode".toCharArray();
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
+ method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
+ method.arguments = null;
+
+ List<Statement> statements = new ArrayList<Statement>();
+ List<Expression> intoResult = new ArrayList<Expression>();
+
+ final char[] PRIME = "PRIME".toCharArray();
+ final char[] RESULT = "result".toCharArray();
+ final boolean isEmpty = fields.isEmpty();
+
+ /* final int PRIME = 31; */ {
+ /* Without fields, PRIME isn't used, and that would trigger a 'local variable not used' warning. */
+ if (!isEmpty || callSuper) {
+ LocalDeclaration primeDecl = new LocalDeclaration(PRIME, pS, pE);
+ Eclipse.setGeneratedBy(primeDecl, source);
+ primeDecl.modifiers |= Modifier.FINAL;
+ primeDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0);
+ primeDecl.type.sourceStart = pS; primeDecl.type.sourceEnd = pE;
+ Eclipse.setGeneratedBy(primeDecl.type, source);
+ primeDecl.initialization = new IntLiteral("31".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(primeDecl.initialization, source);
+ statements.add(primeDecl);
+ }
+ }
+
+ /* int result = 1; */ {
+ LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE);
+ Eclipse.setGeneratedBy(resultDecl, source);
+ resultDecl.initialization = new IntLiteral("1".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(resultDecl.initialization, source);
+ resultDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0);
+ resultDecl.type.sourceStart = pS; resultDecl.type.sourceEnd = pE;
+ Eclipse.setGeneratedBy(resultDecl.type, source);
+ statements.add(resultDecl);
+ }
+
+ if (callSuper) {
+ MessageSend callToSuper = new MessageSend();
+ Eclipse.setGeneratedBy(callToSuper, source);
+ callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE;
+ callToSuper.receiver = new SuperReference(pS, pE);
+ Eclipse.setGeneratedBy(callToSuper.receiver, source);
+ callToSuper.selector = "hashCode".toCharArray();
+ intoResult.add(callToSuper);
+ }
+
+ int tempCounter = 0;
+ for (EclipseNode field : fields) {
+ FieldDeclaration f = (FieldDeclaration) field.get();
+ char[] token = f.type.getLastToken();
+ if (f.type.dimensions() == 0 && token != null) {
+ if (Arrays.equals(TypeConstants.FLOAT, token)) {
+ /* Float.floatToIntBits(fieldName) */
+ MessageSend floatToIntBits = new MessageSend();
+ floatToIntBits.sourceStart = pS; floatToIntBits.sourceEnd = pE;
+ Eclipse.setGeneratedBy(floatToIntBits, source);
+ floatToIntBits.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA_LANG_FLOAT);
+ floatToIntBits.selector = "floatToIntBits".toCharArray();
+ floatToIntBits.arguments = new Expression[] { generateFieldReference(f.name, source) };
+ intoResult.add(floatToIntBits);
+ } else if (Arrays.equals(TypeConstants.DOUBLE, token)) {
+ /* longToIntForHashCode(Double.doubleToLongBits(fieldName)) */
+ MessageSend doubleToLongBits = new MessageSend();
+ doubleToLongBits.sourceStart = pS; doubleToLongBits.sourceEnd = pE;
+ Eclipse.setGeneratedBy(doubleToLongBits, source);
+ doubleToLongBits.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA_LANG_DOUBLE);
+ doubleToLongBits.selector = "doubleToLongBits".toCharArray();
+ doubleToLongBits.arguments = new Expression[] { generateFieldReference(f.name, source) };
+ final char[] tempName = ("temp" + ++tempCounter).toCharArray();
+ LocalDeclaration tempVar = new LocalDeclaration(tempName, pS, pE);
+ Eclipse.setGeneratedBy(tempVar, source);
+ tempVar.initialization = doubleToLongBits;
+ tempVar.type = TypeReference.baseTypeReference(TypeIds.T_long, 0);
+ tempVar.type.sourceStart = pS; tempVar.type.sourceEnd = pE;
+ Eclipse.setGeneratedBy(tempVar.type, source);
+ tempVar.modifiers = Modifier.FINAL;
+ statements.add(tempVar);
+ SingleNameReference copy1 = new SingleNameReference(tempName, p);
+ Eclipse.setGeneratedBy(copy1, source);
+ SingleNameReference copy2 = new SingleNameReference(tempName, p);
+ Eclipse.setGeneratedBy(copy2, source);
+ intoResult.add(longToIntForHashCode(copy1, copy2, source));
+ } else if (Arrays.equals(TypeConstants.BOOLEAN, token)) {
+ /* booleanField ? 1231 : 1237 */
+ IntLiteral int1231 = new IntLiteral("1231".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(int1231, source);
+ IntLiteral int1237 = new IntLiteral("1237".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(int1237, source);
+ ConditionalExpression int1231or1237 = new ConditionalExpression(
+ generateFieldReference(f.name, source), int1231, int1237);
+ Eclipse.setGeneratedBy(int1231or1237, source);
+ intoResult.add(int1231or1237);
+ } else if (Arrays.equals(TypeConstants.LONG, token)) {
+ intoResult.add(longToIntForHashCode(generateFieldReference(f.name, source), generateFieldReference(f.name, source), source));
+ } else if (BUILT_IN_TYPES.contains(new String(token))) {
+ intoResult.add(generateFieldReference(f.name, source));
+ } else /* objects */ {
+ /* this.fieldName == null ? 0 : this.fieldName.hashCode() */
+ MessageSend hashCodeCall = new MessageSend();
+ hashCodeCall.sourceStart = pS; hashCodeCall.sourceEnd = pE;
+ Eclipse.setGeneratedBy(hashCodeCall, source);
+ hashCodeCall.receiver = generateFieldReference(f.name, source);
+ hashCodeCall.selector = "hashCode".toCharArray();
+ NullLiteral nullLiteral = new NullLiteral(pS, pE);
+ Eclipse.setGeneratedBy(nullLiteral, source);
+ EqualExpression objIsNull = new EqualExpression(
+ generateFieldReference(f.name, source), nullLiteral, OperatorIds.EQUAL_EQUAL);
+ Eclipse.setGeneratedBy(objIsNull, source);
+ IntLiteral int0 = new IntLiteral("0".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(int0, source);
+ ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, int0, hashCodeCall);
+ nullOrHashCode.sourceStart = pS; nullOrHashCode.sourceEnd = pE;
+ Eclipse.setGeneratedBy(nullOrHashCode, source);
+ intoResult.add(nullOrHashCode);
+ }
+ } else if (f.type.dimensions() > 0 && token != null) {
+ /* Arrays.deepHashCode(array) //just hashCode for simple arrays */
+ MessageSend arraysHashCodeCall = new MessageSend();
+ arraysHashCodeCall.sourceStart = pS; arraysHashCodeCall.sourceEnd = pE;
+ Eclipse.setGeneratedBy(arraysHashCodeCall, source);
+ arraysHashCodeCall.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray());
+ if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(token))) {
+ arraysHashCodeCall.selector = "deepHashCode".toCharArray();
+ } else {
+ arraysHashCodeCall.selector = "hashCode".toCharArray();
+ }
+ arraysHashCodeCall.arguments = new Expression[] { generateFieldReference(f.name, source) };
+ intoResult.add(arraysHashCodeCall);
+ }
+ }
+
+ /* fold each intoResult entry into:
+ result = result * PRIME + (item); */ {
+ for (Expression ex : intoResult) {
+ SingleNameReference resultRef = new SingleNameReference(RESULT, p);
+ Eclipse.setGeneratedBy(resultRef, source);
+ SingleNameReference primeRef = new SingleNameReference(PRIME, p);
+ Eclipse.setGeneratedBy(primeRef, source);
+ BinaryExpression multiplyByPrime = new BinaryExpression(resultRef, primeRef, OperatorIds.MULTIPLY);
+ multiplyByPrime.sourceStart = pS; multiplyByPrime.sourceEnd = pE;
+ Eclipse.setGeneratedBy(multiplyByPrime, source);
+ BinaryExpression addItem = new BinaryExpression(multiplyByPrime, ex, OperatorIds.PLUS);
+ addItem.sourceStart = pS; addItem.sourceEnd = pE;
+ Eclipse.setGeneratedBy(addItem, source);
+ resultRef = new SingleNameReference(RESULT, p);
+ Eclipse.setGeneratedBy(resultRef, source);
+ Assignment assignment = new Assignment(resultRef, addItem, pE);
+ assignment.sourceStart = pS; assignment.sourceEnd = pE;
+ Eclipse.setGeneratedBy(assignment, source);
+ statements.add(assignment);
+ }
+ }
+
+ /* return result; */ {
+ SingleNameReference resultRef = new SingleNameReference(RESULT, p);
+ Eclipse.setGeneratedBy(resultRef, source);
+ ReturnStatement returnStatement = new ReturnStatement(resultRef, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ statements.add(returnStatement);
+ }
+ method.statements = statements.toArray(new Statement[statements.size()]);
+ return method;
+ }
+
+ private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source) {
+ int pS = source.sourceStart; int pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+
+ MethodDeclaration method = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ Eclipse.setGeneratedBy(method, source);
+ method.modifiers = EclipseHandlerUtil.toEclipseModifier(AccessLevel.PUBLIC);
+ method.returnType = TypeReference.baseTypeReference(TypeIds.T_boolean, 0);
+ method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE;
+ Eclipse.setGeneratedBy(method.returnType, source);
+ method.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)};
+ method.selector = "equals".toCharArray();
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
+ method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
+ TypeReference objectRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { p, p, p });
+ Eclipse.setGeneratedBy(objectRef, source);
+ method.arguments = new Argument[] {new Argument(new char[] { 'o' }, 0, objectRef, Modifier.FINAL)};
+ method.arguments[0].sourceStart = pS; method.arguments[0].sourceEnd = pE;
+ Eclipse.setGeneratedBy(method.arguments[0], source);
+
+ List<Statement> statements = new ArrayList<Statement>();
+
+ /* if (o == this) return true; */ {
+ SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p);
+ Eclipse.setGeneratedBy(oRef, source);
+ ThisReference thisRef = new ThisReference(pS, pE);
+ Eclipse.setGeneratedBy(thisRef, source);
+ EqualExpression otherEqualsThis = new EqualExpression(oRef, thisRef, OperatorIds.EQUAL_EQUAL);
+ Eclipse.setGeneratedBy(otherEqualsThis, source);
+
+ TrueLiteral trueLiteral = new TrueLiteral(pS, pE);
+ Eclipse.setGeneratedBy(trueLiteral, source);
+ ReturnStatement returnTrue = new ReturnStatement(trueLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnTrue, source);
+ IfStatement ifOtherEqualsThis = new IfStatement(otherEqualsThis, returnTrue, pS, pE);
+ Eclipse.setGeneratedBy(ifOtherEqualsThis, source);
+ statements.add(ifOtherEqualsThis);
+ }
+
+ /* if (o == null) return false; */ {
+ SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p);
+ Eclipse.setGeneratedBy(oRef, source);
+ NullLiteral nullLiteral = new NullLiteral(pS, pE);
+ Eclipse.setGeneratedBy(nullLiteral, source);
+ EqualExpression otherEqualsNull = new EqualExpression(oRef, nullLiteral, OperatorIds.EQUAL_EQUAL);
+ Eclipse.setGeneratedBy(otherEqualsNull, source);
+
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnFalse, source);
+ IfStatement ifOtherEqualsNull = new IfStatement(otherEqualsNull, returnFalse, pS, pE);
+ Eclipse.setGeneratedBy(ifOtherEqualsNull, source);
+ statements.add(ifOtherEqualsNull);
+ }
+
+ /* if (o.getClass() != getClass()) return false; */ {
+ MessageSend otherGetClass = new MessageSend();
+ otherGetClass.sourceStart = pS; otherGetClass.sourceEnd = pE;
+ Eclipse.setGeneratedBy(otherGetClass, source);
+ otherGetClass.receiver = new SingleNameReference(new char[] { 'o' }, p);
+ Eclipse.setGeneratedBy(otherGetClass.receiver, source);
+ otherGetClass.selector = "getClass".toCharArray();
+ MessageSend thisGetClass = new MessageSend();
+ thisGetClass.sourceStart = pS; thisGetClass.sourceEnd = pE;
+ Eclipse.setGeneratedBy(thisGetClass, source);
+ thisGetClass.receiver = new ThisReference(pS, pE);
+ Eclipse.setGeneratedBy(thisGetClass.receiver, source);
+ thisGetClass.selector = "getClass".toCharArray();
+ EqualExpression classesNotEqual = new EqualExpression(otherGetClass, thisGetClass, OperatorIds.NOT_EQUAL);
+ Eclipse.setGeneratedBy(classesNotEqual, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnFalse, source);
+ IfStatement ifClassesNotEqual = new IfStatement(classesNotEqual, returnFalse, pS, pE);
+ Eclipse.setGeneratedBy(ifClassesNotEqual, source);
+ statements.add(ifClassesNotEqual);
+ }
+
+ char[] otherN = "other".toCharArray();
+
+ /* if (!super.equals(o)) return false; */
+ if (callSuper) {
+ MessageSend callToSuper = new MessageSend();
+ callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE;
+ Eclipse.setGeneratedBy(callToSuper, source);
+ callToSuper.receiver = new SuperReference(pS, pE);
+ Eclipse.setGeneratedBy(callToSuper.receiver, source);
+ callToSuper.selector = "equals".toCharArray();
+ SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p);
+ Eclipse.setGeneratedBy(oRef, source);
+ callToSuper.arguments = new Expression[] {oRef};
+ Expression superNotEqual = new UnaryExpression(callToSuper, OperatorIds.NOT);
+ Eclipse.setGeneratedBy(superNotEqual, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnFalse, source);
+ IfStatement ifSuperEquals = new IfStatement(superNotEqual, returnFalse, pS, pE);
+ Eclipse.setGeneratedBy(ifSuperEquals, source);
+ statements.add(ifSuperEquals);
+ }
+
+ TypeDeclaration typeDecl = (TypeDeclaration)type.get();
+ /* MyType<?> other = (MyType<?>) o; */ {
+ if (!fields.isEmpty()) {
+ LocalDeclaration other = new LocalDeclaration(otherN, pS, pE);
+ Eclipse.setGeneratedBy(other, source);
+ char[] typeName = typeDecl.name;
+ Expression targetType;
+ if (typeDecl.typeParameters == null || typeDecl.typeParameters.length == 0) {
+ targetType = new SingleNameReference(((TypeDeclaration)type.get()).name, p);
+ Eclipse.setGeneratedBy(targetType, source);
+ other.type = new SingleTypeReference(typeName, p);
+ Eclipse.setGeneratedBy(other.type, source);
+ } else {
+ TypeReference[] typeArgs = new TypeReference[typeDecl.typeParameters.length];
+ for (int i = 0; i < typeArgs.length; i++) {
+ typeArgs[i] = new Wildcard(Wildcard.UNBOUND);
+ typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE;
+ Eclipse.setGeneratedBy(typeArgs[i], source);
+ }
+ targetType = new ParameterizedSingleTypeReference(typeName, typeArgs, 0, p);
+ Eclipse.setGeneratedBy(targetType, source);
+ other.type = new ParameterizedSingleTypeReference(typeName, copyTypes(typeArgs, source), 0, p);
+ Eclipse.setGeneratedBy(other.type, source);
+ }
+ NameReference oRef = new SingleNameReference(new char[] { 'o' }, p);
+ Eclipse.setGeneratedBy(oRef, source);
+ other.initialization = new CastExpression(oRef, targetType);
+ Eclipse.setGeneratedBy(other.initialization, source);
+ statements.add(other);
+ }
+ }
+
+ for (EclipseNode field : fields) {
+ FieldDeclaration f = (FieldDeclaration) field.get();
+ char[] token = f.type.getLastToken();
+ if (f.type.dimensions() == 0 && token != null) {
+ if (Arrays.equals(TypeConstants.FLOAT, token)) {
+ statements.add(generateCompareFloatOrDouble(otherN, "Float".toCharArray(), f.name, source));
+ } else if (Arrays.equals(TypeConstants.DOUBLE, token)) {
+ statements.add(generateCompareFloatOrDouble(otherN, "Double".toCharArray(), f.name, source));
+ } else if (BUILT_IN_TYPES.contains(new String(token))) {
+ NameReference fieldRef = new SingleNameReference(f.name, p);
+ Eclipse.setGeneratedBy(fieldRef, source);
+ EqualExpression fieldsNotEqual = new EqualExpression(fieldRef,
+ generateQualifiedNameRef(source, otherN, f.name), OperatorIds.NOT_EQUAL);
+ Eclipse.setGeneratedBy(fieldsNotEqual, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ IfStatement ifStatement = new IfStatement(fieldsNotEqual, returnStatement, pS, pE);
+ Eclipse.setGeneratedBy(ifStatement, source);
+ statements.add(ifStatement);
+ } else /* objects */ {
+ NameReference fieldNameRef = new SingleNameReference(f.name, p);
+ Eclipse.setGeneratedBy(fieldNameRef, source);
+ NullLiteral nullLiteral = new NullLiteral(pS, pE);
+ Eclipse.setGeneratedBy(nullLiteral, source);
+ EqualExpression fieldIsNull = new EqualExpression(fieldNameRef, nullLiteral, OperatorIds.EQUAL_EQUAL);
+ nullLiteral = new NullLiteral(pS, pE);
+ Eclipse.setGeneratedBy(nullLiteral, source);
+ EqualExpression otherFieldIsntNull = new EqualExpression(
+ generateQualifiedNameRef(source, otherN, f.name),
+ nullLiteral, OperatorIds.NOT_EQUAL);
+ MessageSend equalsCall = new MessageSend();
+ equalsCall.sourceStart = pS; equalsCall.sourceEnd = pE;
+ Eclipse.setGeneratedBy(equalsCall, source);
+ equalsCall.receiver = new SingleNameReference(f.name, p);
+ Eclipse.setGeneratedBy(equalsCall.receiver, source);
+ equalsCall.selector = "equals".toCharArray();
+ equalsCall.arguments = new Expression[] { generateQualifiedNameRef(source, otherN, f.name) };
+ UnaryExpression fieldsNotEqual = new UnaryExpression(equalsCall, OperatorIds.NOT);
+ fieldsNotEqual.sourceStart = pS; fieldsNotEqual.sourceEnd = pE;
+ Eclipse.setGeneratedBy(fieldsNotEqual, source);
+ ConditionalExpression fullEquals = new ConditionalExpression(fieldIsNull, otherFieldIsntNull, fieldsNotEqual);
+ fullEquals.sourceStart = pS; fullEquals.sourceEnd = pE;
+ Eclipse.setGeneratedBy(fullEquals, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ IfStatement ifStatement = new IfStatement(fullEquals, returnStatement, pS, pE);
+ Eclipse.setGeneratedBy(ifStatement, source);
+ statements.add(ifStatement);
+ }
+ } else if (f.type.dimensions() > 0 && token != null) {
+ MessageSend arraysEqualCall = new MessageSend();
+ arraysEqualCall.sourceStart = pS; arraysEqualCall.sourceEnd = pE;
+ Eclipse.setGeneratedBy(arraysEqualCall, source);
+ arraysEqualCall.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray());
+ if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(token))) {
+ arraysEqualCall.selector = "deepEquals".toCharArray();
+ } else {
+ arraysEqualCall.selector = "equals".toCharArray();
+ }
+ NameReference fieldNameRef = new SingleNameReference(f.name, p);
+ Eclipse.setGeneratedBy(fieldNameRef, source);
+ arraysEqualCall.arguments = new Expression[] { fieldNameRef, generateQualifiedNameRef(source, otherN, f.name) };
+ UnaryExpression arraysNotEqual = new UnaryExpression(arraysEqualCall, OperatorIds.NOT);
+ arraysNotEqual.sourceStart = pS; arraysNotEqual.sourceEnd = pE;
+ Eclipse.setGeneratedBy(arraysNotEqual, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ IfStatement ifStatement = new IfStatement(arraysNotEqual, returnStatement, pS, pE);
+ Eclipse.setGeneratedBy(ifStatement, source);
+ statements.add(ifStatement);
+ }
+ }
+
+ /* return true; */ {
+ TrueLiteral trueLiteral = new TrueLiteral(pS, pE);
+ Eclipse.setGeneratedBy(trueLiteral, source);
+ ReturnStatement returnStatement = new ReturnStatement(trueLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ statements.add(returnStatement);
+ }
+ method.statements = statements.toArray(new Statement[statements.size()]);
+ return method;
+ }
+
+ private IfStatement generateCompareFloatOrDouble(char[] otherN, char[] floatOrDouble, char[] fieldName, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+ /* if (Float.compare(fieldName, other.fieldName) != 0) return false */
+ MessageSend floatCompare = new MessageSend();
+ floatCompare.sourceStart = pS; floatCompare.sourceEnd = pE;
+ Eclipse.setGeneratedBy(floatCompare, source);
+ floatCompare.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.LANG, floatOrDouble);
+ floatCompare.selector = "compare".toCharArray();
+ NameReference fieldNameRef = new SingleNameReference(fieldName, p);
+ Eclipse.setGeneratedBy(fieldNameRef, source);
+ floatCompare.arguments = new Expression[] {fieldNameRef, generateQualifiedNameRef(source, otherN, fieldName)};
+ IntLiteral int0 = new IntLiteral(new char[] {'0'}, pS, pE);
+ Eclipse.setGeneratedBy(int0, source);
+ EqualExpression ifFloatCompareIsNot0 = new EqualExpression(floatCompare, int0, OperatorIds.NOT_EQUAL);
+ ifFloatCompareIsNot0.sourceStart = pS; ifFloatCompareIsNot0.sourceEnd = pE;
+ Eclipse.setGeneratedBy(ifFloatCompareIsNot0, source);
+ FalseLiteral falseLiteral = new FalseLiteral(pS, pE);
+ Eclipse.setGeneratedBy(falseLiteral, source);
+ ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE);
+ Eclipse.setGeneratedBy(returnFalse, source);
+ IfStatement ifStatement = new IfStatement(ifFloatCompareIsNot0, returnFalse, pS, pE);
+ Eclipse.setGeneratedBy(ifStatement, source);
+ return ifStatement;
+ }
+
+ /** Give 2 clones! */
+ private Expression longToIntForHashCode(Reference ref1, Reference ref2, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ /* (int)(ref >>> 32 ^ ref) */
+ IntLiteral int32 = new IntLiteral("32".toCharArray(), pS, pE);
+ Eclipse.setGeneratedBy(int32, source);
+ BinaryExpression higherBits = new BinaryExpression(ref1, int32, OperatorIds.UNSIGNED_RIGHT_SHIFT);
+ Eclipse.setGeneratedBy(higherBits, source);
+ BinaryExpression xorParts = new BinaryExpression(ref2, higherBits, OperatorIds.XOR);
+ Eclipse.setGeneratedBy(xorParts, source);
+ TypeReference intRef = TypeReference.baseTypeReference(TypeIds.T_int, 0);
+ intRef.sourceStart = pS; intRef.sourceEnd = pE;
+ Eclipse.setGeneratedBy(intRef, source);
+ CastExpression expr = new CastExpression(xorParts, intRef);
+ expr.sourceStart = pS; expr.sourceEnd = pE;
+ Eclipse.setGeneratedBy(expr, source);
+ return expr;
+ }
+
+ private Reference generateFieldReference(char[] fieldName, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+ FieldReference thisX = new FieldReference(("this." + new String(fieldName)).toCharArray(), p);
+ Eclipse.setGeneratedBy(thisX, source);
+ thisX.receiver = new ThisReference(pS, pE);
+ Eclipse.setGeneratedBy(thisX.receiver, source);
+ thisX.token = fieldName;
+ return thisX;
+ }
+
+ private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+
+ NameReference ref;
+
+ if (varNames.length > 1) ref = new QualifiedNameReference(varNames, new long[varNames.length], pS, pE);
+ else ref = new SingleNameReference(varNames[0], p);
+ Eclipse.setGeneratedBy(ref, source);
+ return ref;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java
new file mode 100644
index 00000000..4a9930e3
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleGetter.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.*;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+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.classfmt.ClassFileConstants;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.Getter} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleGetter implements EclipseAnnotationHandler<Getter> {
+ /**
+ * Generates a getter on the stated field.
+ *
+ * Used by {@link HandleData}.
+ *
+ * The difference between this call and the handle method is as follows:
+ *
+ * If there is a {@code lombok.Getter} annotation on the field, it is used and the
+ * same rules apply (e.g. warning if the method already exists, stated access level applies).
+ * If not, the getter is still generated if it isn't already there, though there will not
+ * be a warning if its already there. The default access level is used.
+ */
+ public void generateGetterForField(EclipseNode fieldNode, ASTNode pos) {
+ for (EclipseNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (annotationTypeMatches(Getter.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ createGetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false);
+ }
+
+ public boolean handle(AnnotationValues<Getter> annotation, Annotation ast, EclipseNode annotationNode) {
+ EclipseNode fieldNode = annotationNode.up();
+ AccessLevel level = annotation.getInstance().value();
+ if (level == AccessLevel.NONE) return true;
+
+ return createGetterForField(level, fieldNode, annotationNode, annotationNode.get(), true);
+ }
+
+ private boolean createGetterForField(AccessLevel level,
+ EclipseNode fieldNode, EclipseNode errorNode, ASTNode source, boolean whineIfExists) {
+ if (fieldNode.getKind() != Kind.FIELD) {
+ errorNode.addError("@Getter is only supported on a field.");
+ return true;
+ }
+
+ FieldDeclaration field = (FieldDeclaration) fieldNode.get();
+ TypeReference fieldType = copyType(field.type, source);
+ String fieldName = new String(field.name);
+ boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ String getterName = TransformationsUtil.toGetterName(fieldName, isBoolean);
+
+ int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic);
+
+ for (String altName : TransformationsUtil.toAllGetterNames(fieldName, isBoolean)) {
+ switch (methodExists(altName, fieldNode)) {
+ case EXISTS_BY_LOMBOK:
+ return true;
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ String altNameExpl = "";
+ if (!altName.equals(getterName)) altNameExpl = String.format(" (%s)", altName);
+ errorNode.addWarning(
+ String.format("Not generating %s(): A method with that name already exists%s", getterName, altNameExpl));
+ }
+ return true;
+ default:
+ case NOT_EXISTS:
+ //continue scanning the other alt names.
+ }
+ }
+
+ MethodDeclaration method = generateGetter((TypeDeclaration) fieldNode.up().get(), field, getterName, modifier, source);
+ Annotation[] copiedAnnotations = copyAnnotations(
+ findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN),
+ findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN), source);
+ if (copiedAnnotations.length != 0) {
+ method.annotations = copiedAnnotations;
+ }
+
+ injectMethod(fieldNode.up(), method);
+
+ return true;
+ }
+
+ private MethodDeclaration generateGetter(TypeDeclaration parent, FieldDeclaration field, String name,
+ int modifier, ASTNode source) {
+ MethodDeclaration method = new MethodDeclaration(parent.compilationResult);
+ Eclipse.setGeneratedBy(method, source);
+ method.modifiers = modifier;
+ method.returnType = copyType(field.type, source);
+ method.annotations = null;
+ method.arguments = null;
+ method.selector = name.toCharArray();
+ method.binding = null;
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ Expression fieldExpression = new SingleNameReference(field.name, ((long)field.declarationSourceStart << 32) | field.declarationSourceEnd);
+ Eclipse.setGeneratedBy(fieldExpression, source);
+ Statement returnStatement = new ReturnStatement(fieldExpression, field.sourceStart, field.sourceEnd);
+ Eclipse.setGeneratedBy(returnStatement, source);
+ method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
+ method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
+ method.statements = new Statement[] { returnStatement };
+ return method;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java
new file mode 100644
index 00000000..580a54a2
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.mangosdk.spi.ProviderFor;
+
+import lombok.Lombok;
+import lombok.core.AnnotationValues;
+import lombok.core.PrintAST;
+import lombok.eclipse.EclipseASTVisitor;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+/**
+ * Handles the {@code lombok.core.PrintAST} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandlePrintAST implements EclipseAnnotationHandler<PrintAST> {
+ public boolean handle(AnnotationValues<PrintAST> annotation, Annotation ast, EclipseNode annotationNode) {
+ if (!annotationNode.isCompleteParse()) return false;
+
+ PrintStream stream = System.out;
+ String fileName = annotation.getInstance().outfile();
+ if (fileName.length() > 0) try {
+ stream = new PrintStream(new File(fileName));
+ } catch (FileNotFoundException e) {
+ Lombok.sneakyThrow(e);
+ }
+
+ annotationNode.up().traverse(new EclipseASTVisitor.Printer(annotation.getInstance().printContent(), stream));
+ return true;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
new file mode 100644
index 00000000..9bd10af3
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.*;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.lang.reflect.Modifier;
+
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.Setter} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleSetter implements EclipseAnnotationHandler<Setter> {
+ /**
+ * Generates a setter on the stated field.
+ *
+ * Used by {@link HandleData}.
+ *
+ * The difference between this call and the handle method is as follows:
+ *
+ * If there is a {@code lombok.Setter} annotation on the field, it is used and the
+ * same rules apply (e.g. warning if the method already exists, stated access level applies).
+ * If not, the setter is still generated if it isn't already there, though there will not
+ * be a warning if its already there. The default access level is used.
+ */
+ public void generateSetterForField(EclipseNode fieldNode, ASTNode pos) {
+ for (EclipseNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (annotationTypeMatches(Setter.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false);
+ }
+
+ public boolean handle(AnnotationValues<Setter> annotation, Annotation ast, EclipseNode annotationNode) {
+ EclipseNode fieldNode = annotationNode.up();
+ if (fieldNode.getKind() != Kind.FIELD) return false;
+ AccessLevel level = annotation.getInstance().value();
+ if (level == AccessLevel.NONE) return true;
+
+ return createSetterForField(level, fieldNode, annotationNode, annotationNode.get(), true);
+ }
+
+ private boolean createSetterForField(AccessLevel level,
+ EclipseNode fieldNode, EclipseNode errorNode, ASTNode pos, boolean whineIfExists) {
+ if (fieldNode.getKind() != Kind.FIELD) {
+ errorNode.addError("@Setter is only supported on a field.");
+ return true;
+ }
+
+ FieldDeclaration field = (FieldDeclaration) fieldNode.get();
+ String setterName = TransformationsUtil.toSetterName(new String(field.name));
+
+ int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic);
+
+ switch (methodExists(setterName, fieldNode)) {
+ case EXISTS_BY_LOMBOK:
+ return true;
+ case EXISTS_BY_USER:
+ if (whineIfExists) errorNode.addWarning(
+ String.format("Not generating %s(%s %s): A method with that name already exists",
+ setterName, field.type, new String(field.name)));
+ return true;
+ default:
+ case NOT_EXISTS:
+ //continue with creating the setter
+ }
+
+ MethodDeclaration method = generateSetter((TypeDeclaration) fieldNode.up().get(), field, setterName, modifier, pos);
+
+ injectMethod(fieldNode.up(), method);
+
+ return true;
+ }
+
+ private MethodDeclaration generateSetter(TypeDeclaration parent, FieldDeclaration field, String name,
+ int modifier, ASTNode source) {
+
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+ MethodDeclaration method = new MethodDeclaration(parent.compilationResult);
+ Eclipse.setGeneratedBy(method, source);
+ method.modifiers = modifier;
+ method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0);
+ method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE;
+ Eclipse.setGeneratedBy(method.returnType, source);
+ method.annotations = null;
+ Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL);
+ param.sourceStart = pS; param.sourceEnd = pE;
+ Eclipse.setGeneratedBy(param, source);
+ method.arguments = new Argument[] { param };
+ method.selector = name.toCharArray();
+ method.binding = null;
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ FieldReference thisX = new FieldReference(field.name, p);
+ Eclipse.setGeneratedBy(thisX, source);
+ thisX.receiver = new ThisReference(source.sourceStart, source.sourceEnd);
+ Eclipse.setGeneratedBy(thisX.receiver, source);
+ NameReference fieldNameRef = new SingleNameReference(field.name, p);
+ Eclipse.setGeneratedBy(fieldNameRef, source);
+ Assignment assignment = new Assignment(thisX, fieldNameRef, (int)p);
+ assignment.sourceStart = pS; assignment.sourceEnd = pE;
+ Eclipse.setGeneratedBy(assignment, source);
+ method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
+ method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
+
+ Annotation[] nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
+ Annotation[] nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);
+ if (nonNulls.length == 0) {
+ method.statements = new Statement[] { assignment };
+ } else {
+ Statement nullCheck = generateNullCheck(field, source);
+ if (nullCheck != null) method.statements = new Statement[] { nullCheck, assignment };
+ else method.statements = new Statement[] { assignment };
+ }
+ Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables, source);
+ if (copiedAnnotations.length != 0) param.annotations = copiedAnnotations;
+ return method;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java
new file mode 100644
index 00000000..38f22b2a
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.SneakyThrows;
+import lombok.core.AnnotationValues;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.HandleSneakyThrows} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleSneakyThrows implements EclipseAnnotationHandler<SneakyThrows> {
+ private static class DeclaredException {
+ final String exceptionName;
+ final ASTNode node;
+
+ DeclaredException(String exceptionName, ASTNode node) {
+ this.exceptionName = exceptionName;
+ this.node = node;
+ }
+
+ public long getPos() {
+ return (long)node.sourceStart << 32 | node.sourceEnd;
+ }
+ }
+
+ @Override public boolean handle(AnnotationValues<SneakyThrows> annotation, Annotation source, EclipseNode annotationNode) {
+ List<String> exceptionNames = annotation.getRawExpressions("value");
+ List<DeclaredException> exceptions = new ArrayList<DeclaredException>();
+
+ MemberValuePair[] memberValuePairs = source.memberValuePairs();
+ if (memberValuePairs == null || memberValuePairs.length == 0) {
+ exceptions.add(new DeclaredException("java.lang.Throwable", source));
+ } else {
+ Expression arrayOrSingle = memberValuePairs[0].value;
+ final Expression[] exceptionNameNodes;
+ if (arrayOrSingle instanceof ArrayInitializer) {
+ exceptionNameNodes = ((ArrayInitializer)arrayOrSingle).expressions;
+ } else exceptionNameNodes = new Expression[] { arrayOrSingle };
+
+ if (exceptionNames.size() != exceptionNameNodes.length) {
+ annotationNode.addError(
+ "LOMBOK BUG: The number of exception classes in the annotation isn't the same pre- and post- guessing.");
+ }
+
+ int idx = 0;
+ for (String exceptionName : exceptionNames) {
+ if (exceptionName.endsWith(".class")) exceptionName = exceptionName.substring(0, exceptionName.length() - 6);
+ exceptions.add(new DeclaredException(exceptionName, exceptionNameNodes[idx++]));
+ }
+ }
+
+
+ EclipseNode owner = annotationNode.up();
+ switch (owner.getKind()) {
+// case FIELD:
+// return handleField(annotationNode, (FieldDeclaration)owner.get(), exceptions);
+ case METHOD:
+ return handleMethod(annotationNode, (AbstractMethodDeclaration)owner.get(), exceptions);
+ default:
+ annotationNode.addError("@SneakyThrows is legal only on methods and constructors.");
+ return true;
+ }
+ }
+
+// private boolean handleField(Node annotation, FieldDeclaration field, List<DeclaredException> exceptions) {
+// if (field.initialization == null) {
+// annotation.addError("@SneakyThrows can only be used on fields with an initialization statement.");
+// return true;
+// }
+//
+// Expression expression = field.initialization;
+// Statement[] content = new Statement[] {new Assignment(
+// new SingleNameReference(field.name, 0), expression, 0)};
+// field.initialization = null;
+//
+// for (DeclaredException exception : exceptions) {
+// content = new Statement[] { buildTryCatchBlock(content, exception) };
+// }
+//
+// Block block = new Block(0);
+// block.statements = content;
+//
+// Node typeNode = annotation.up().up();
+//
+// Initializer initializer = new Initializer(block, field.modifiers & Modifier.STATIC);
+// initializer.sourceStart = expression.sourceStart;
+// initializer.sourceEnd = expression.sourceEnd;
+// initializer.declarationSourceStart = expression.sourceStart;
+// initializer.declarationSourceEnd = expression.sourceEnd;
+// injectField(typeNode, initializer);
+//
+// typeNode.rebuild();
+//
+// return true;
+// }
+
+ private boolean handleMethod(EclipseNode annotation, AbstractMethodDeclaration method, List<DeclaredException> exceptions) {
+ if (method.isAbstract()) {
+ annotation.addError("@SneakyThrows can only be used on concrete methods.");
+ return true;
+ }
+
+ if (method.statements == null) return false;
+
+ Statement[] contents = method.statements;
+
+ for (DeclaredException exception : exceptions) {
+ contents = new Statement[] { buildTryCatchBlock(contents, exception, exception.node) };
+ }
+
+ method.statements = contents;
+ annotation.up().rebuild();
+
+ return true;
+ }
+
+ private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source) {
+ long p = exception.getPos();
+ int pS = (int)(p >> 32), pE = (int)p;
+
+ TryStatement tryStatement = new TryStatement();
+ Eclipse.setGeneratedBy(tryStatement, source);
+ tryStatement.tryBlock = new Block(0);
+ tryStatement.tryBlock.sourceStart = pS; tryStatement.tryBlock.sourceEnd = pE;
+ Eclipse.setGeneratedBy(tryStatement.tryBlock, source);
+ tryStatement.tryBlock.statements = contents;
+ TypeReference typeReference;
+ if (exception.exceptionName.indexOf('.') == -1) {
+ typeReference = new SingleTypeReference(exception.exceptionName.toCharArray(), p);
+ typeReference.statementEnd = pE;
+ } else {
+ String[] x = exception.exceptionName.split("\\.");
+ char[][] elems = new char[x.length][];
+ long[] poss = new long[x.length];
+ int start = pS;
+ for (int i = 0; i < x.length; i++) {
+ elems[i] = x[i].trim().toCharArray();
+ int end = start + x[i].length();
+ poss[i] = (long)start << 32 | end;
+ start = end + 1;
+ }
+ typeReference = new QualifiedTypeReference(elems, poss);
+ }
+ Eclipse.setGeneratedBy(typeReference, source);
+
+ Argument catchArg = new Argument("$ex".toCharArray(), p, typeReference, Modifier.FINAL);
+ Eclipse.setGeneratedBy(catchArg, source);
+ catchArg.declarationSourceEnd = catchArg.declarationEnd = catchArg.sourceEnd = pE;
+ catchArg.declarationSourceStart = catchArg.modifiersSourceStart = catchArg.sourceStart = pS;
+
+ tryStatement.catchArguments = new Argument[] { catchArg };
+
+ MessageSend sneakyThrowStatement = new MessageSend();
+ Eclipse.setGeneratedBy(sneakyThrowStatement, source);
+ sneakyThrowStatement.receiver = new QualifiedNameReference(new char[][] { "lombok".toCharArray(), "Lombok".toCharArray() }, new long[] { p, p }, pS, pE);
+ Eclipse.setGeneratedBy(sneakyThrowStatement.receiver, source);
+ sneakyThrowStatement.receiver.statementEnd = pE;
+ sneakyThrowStatement.selector = "sneakyThrow".toCharArray();
+ SingleNameReference exRef = new SingleNameReference("$ex".toCharArray(), p);
+ Eclipse.setGeneratedBy(exRef, source);
+ exRef.statementEnd = pE;
+ sneakyThrowStatement.arguments = new Expression[] { exRef };
+ sneakyThrowStatement.nameSourcePosition = p;
+ sneakyThrowStatement.sourceStart = pS;
+ sneakyThrowStatement.sourceEnd = sneakyThrowStatement.statementEnd = pE;
+ Statement rethrowStatement = new ThrowStatement(sneakyThrowStatement, pS, pE);
+ Eclipse.setGeneratedBy(rethrowStatement, source);
+ Block block = new Block(0);
+ block.sourceStart = pS;
+ block.sourceEnd = pE;
+ Eclipse.setGeneratedBy(block, source);
+ block.statements = new Statement[] { rethrowStatement };
+ tryStatement.catchBlocks = new Block[] { block };
+ tryStatement.sourceStart = pS;
+ tryStatement.sourceEnd = pE;
+ return tryStatement;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleSynchronized.java b/src/core/lombok/eclipse/handlers/HandleSynchronized.java
new file mode 100644
index 00000000..fde36192
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleSynchronized.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.lang.reflect.Modifier;
+
+import lombok.Synchronized;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult;
+
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code lombok.Synchronized} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleSynchronized implements EclipseAnnotationHandler<Synchronized> {
+ private static final char[] INSTANCE_LOCK_NAME = "$lock".toCharArray();
+ private static final char[] STATIC_LOCK_NAME = "$LOCK".toCharArray();
+
+ @Override public boolean handle(AnnotationValues<Synchronized> annotation, Annotation source, EclipseNode annotationNode) {
+ int p1 = source.sourceStart -1;
+ int p2 = source.sourceStart -2;
+ long pos = (((long)p1) << 32) | p2;
+ EclipseNode methodNode = annotationNode.up();
+ if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) {
+ annotationNode.addError("@Synchronized is legal only on methods.");
+ return true;
+ }
+
+ MethodDeclaration method = (MethodDeclaration)methodNode.get();
+ if (method.isAbstract()) {
+ annotationNode.addError("@Synchronized is legal only on concrete methods.");
+ return true;
+ }
+
+ char[] lockName = annotation.getInstance().value().toCharArray();
+ boolean autoMake = false;
+ if (lockName.length == 0) {
+ autoMake = true;
+ lockName = method.isStatic() ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME;
+ }
+
+ if (fieldExists(new String(lockName), methodNode) == MemberExistsResult.NOT_EXISTS) {
+ if (!autoMake) {
+ annotationNode.addError("The field " + new String(lockName) + " does not exist.");
+ return true;
+ }
+ FieldDeclaration fieldDecl = new FieldDeclaration(lockName, 0, -1);
+ Eclipse.setGeneratedBy(fieldDecl, source);
+ fieldDecl.declarationSourceEnd = -1;
+
+ fieldDecl.modifiers = (method.isStatic() ? Modifier.STATIC : 0) | Modifier.FINAL | Modifier.PRIVATE;
+
+ //We use 'new Object[0];' because quite unlike 'new Object();', empty arrays *ARE* serializable!
+ ArrayAllocationExpression arrayAlloc = new ArrayAllocationExpression();
+ Eclipse.setGeneratedBy(arrayAlloc, source);
+ arrayAlloc.dimensions = new Expression[] { new IntLiteral(new char[] { '0' }, 0, 0) };
+ Eclipse.setGeneratedBy(arrayAlloc.dimensions[0], source);
+ arrayAlloc.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 });
+ Eclipse.setGeneratedBy(arrayAlloc.type, source);
+ fieldDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 });
+ Eclipse.setGeneratedBy(fieldDecl.type, source);
+ fieldDecl.initialization = arrayAlloc;
+ injectField(annotationNode.up().up(), fieldDecl);
+ }
+
+ if (method.statements == null) return false;
+
+ Block block = new Block(0);
+ Eclipse.setGeneratedBy(block, source);
+ block.statements = method.statements;
+ Expression lockVariable;
+ if (method.isStatic()) lockVariable = new QualifiedNameReference(new char[][] {
+ methodNode.up().getName().toCharArray(), lockName }, new long[] { pos, pos }, p1, p2);
+ else {
+ lockVariable = new FieldReference(lockName, pos);
+ ThisReference thisReference = new ThisReference(p1, p2);
+ Eclipse.setGeneratedBy(thisReference, source);
+ ((FieldReference)lockVariable).receiver = thisReference;
+ }
+ Eclipse.setGeneratedBy(lockVariable, source);
+
+ method.statements = new Statement[] {
+ new SynchronizedStatement(lockVariable, block, 0, 0)
+ };
+ Eclipse.setGeneratedBy(method.statements[0], source);
+
+ methodNode.rebuild();
+
+ return true;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java
new file mode 100644
index 00000000..d5a4c398
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleToString.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.AccessLevel;
+import lombok.ToString;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
+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.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+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.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.SuperReference;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.mangosdk.spi.ProviderFor;
+
+/**
+ * Handles the {@code ToString} annotation for eclipse.
+ */
+@ProviderFor(EclipseAnnotationHandler.class)
+public class HandleToString implements EclipseAnnotationHandler<ToString> {
+ private void checkForBogusFieldNames(EclipseNode type, AnnotationValues<ToString> annotation) {
+ if (annotation.isExplicit("exclude")) {
+ for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, false)) {
+ annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
+ }
+ }
+ if (annotation.isExplicit("of")) {
+ for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false)) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
+ }
+
+ public void generateToStringForType(EclipseNode typeNode, EclipseNode errorNode) {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Eclipse.annotationTypeMatches(ToString.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ boolean includeFieldNames = true;
+ try {
+ includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {}
+ generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
+ }
+
+ public boolean handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) {
+ ToString ann = annotation.getInstance();
+ List<String> excludes = Arrays.asList(ann.exclude());
+ List<String> includes = Arrays.asList(ann.of());
+ EclipseNode typeNode = annotationNode.up();
+ Boolean callSuper = ann.callSuper();
+
+ if (!annotation.isExplicit("callSuper")) callSuper = null;
+ if (!annotation.isExplicit("exclude")) excludes = null;
+ if (!annotation.isExplicit("of")) includes = null;
+
+ if (excludes != null && includes != null) {
+ excludes = null;
+ annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored.");
+ }
+
+ checkForBogusFieldNames(typeNode, annotation);
+
+ return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true);
+ }
+
+ public boolean generateToString(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes,
+ boolean includeFieldNames, Boolean callSuper, boolean whineIfExists) {
+ TypeDeclaration typeDecl = null;
+
+ if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get();
+ int modifiers = typeDecl == null ? 0 : typeDecl.modifiers;
+ boolean notAClass = (modifiers &
+ (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0;
+
+ if (typeDecl == null || notAClass) {
+ errorNode.addError("@ToString is only supported on a class.");
+ return false;
+ }
+
+ if (callSuper == null) {
+ try {
+ callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {}
+ }
+
+ List<EclipseNode> nodesForToString = new ArrayList<EclipseNode>();
+ if (includes != null) {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ if (includes.contains(new String(fieldDecl.name))) nodesForToString.add(child);
+ }
+ } else {
+ for (EclipseNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ //Skip static fields.
+ if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue;
+ //Skip excluded fields.
+ if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue;
+ //Skip fields that start with $
+ if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue;
+ nodesForToString.add(child);
+ }
+ }
+
+ switch (methodExists("toString", typeNode)) {
+ case NOT_EXISTS:
+ MethodDeclaration toString = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, errorNode.get());
+ injectMethod(typeNode, toString);
+ return true;
+ case EXISTS_BY_LOMBOK:
+ return true;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating toString(): A method with that name already exists");
+ }
+ return true;
+ }
+ }
+
+ private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields,
+ boolean includeFieldNames, boolean callSuper, ASTNode source) {
+ TypeDeclaration typeDeclaration = (TypeDeclaration)type.get();
+ char[] rawTypeName = typeDeclaration.name;
+ String typeName = rawTypeName == null ? "" : new String(rawTypeName);
+ char[] suffix = ")".toCharArray();
+ String infixS = ", ";
+ char[] infix = infixS.toCharArray();
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+ final int PLUS = OperatorIds.PLUS;
+
+ char[] prefix;
+
+ if (callSuper) {
+ prefix = (typeName + "(super=").toCharArray();
+ } else if (fields.isEmpty()) {
+ prefix = (typeName + "()").toCharArray();
+ } else if (includeFieldNames) {
+ prefix = (typeName + "(" + new String(((FieldDeclaration)fields.iterator().next().get()).name) + "=").toCharArray();
+ } else {
+ prefix = (typeName + "(").toCharArray();
+ }
+
+ boolean first = true;
+ Expression current = new StringLiteral(prefix, pS, pE, 0);
+ Eclipse.setGeneratedBy(current, source);
+
+ if (callSuper) {
+ MessageSend callToSuper = new MessageSend();
+ callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE;
+ Eclipse.setGeneratedBy(callToSuper, source);
+ callToSuper.receiver = new SuperReference(pS, pE);
+ Eclipse.setGeneratedBy(callToSuper, source);
+ callToSuper.selector = "toString".toCharArray();
+ current = new BinaryExpression(current, callToSuper, PLUS);
+ Eclipse.setGeneratedBy(current, source);
+ first = false;
+ }
+
+ for (EclipseNode field : fields) {
+ FieldDeclaration f = (FieldDeclaration)field.get();
+ if (f.name == null || f.type == null) continue;
+
+ Expression ex;
+ if (f.type.dimensions() > 0) {
+ MessageSend arrayToString = new MessageSend();
+ arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE;
+ arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray());
+ arrayToString.arguments = new Expression[] { new SingleNameReference(f.name, p) };
+ Eclipse.setGeneratedBy(arrayToString.arguments[0], source);
+ if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(f.type.getLastToken()))) {
+ arrayToString.selector = "deepToString".toCharArray();
+ } else {
+ arrayToString.selector = "toString".toCharArray();
+ }
+ ex = arrayToString;
+ } else {
+ FieldReference thisX = new FieldReference(f.name, p);
+ thisX.receiver = new ThisReference(source.sourceStart, source.sourceEnd);
+ Eclipse.setGeneratedBy(thisX.receiver, source);
+ ex = thisX;
+ }
+ Eclipse.setGeneratedBy(ex, source);
+
+ if (first) {
+ current = new BinaryExpression(current, ex, PLUS);
+ current.sourceStart = pS; current.sourceEnd = pE;
+ Eclipse.setGeneratedBy(current, source);
+ first = false;
+ continue;
+ }
+
+ StringLiteral fieldNameLiteral;
+ if (includeFieldNames) {
+ char[] namePlusEqualsSign = (infixS + new String(f.name) + "=").toCharArray();
+ fieldNameLiteral = new StringLiteral(namePlusEqualsSign, pS, pE, 0);
+ } else {
+ fieldNameLiteral = new StringLiteral(infix, pS, pE, 0);
+ }
+ Eclipse.setGeneratedBy(fieldNameLiteral, source);
+ current = new BinaryExpression(current, fieldNameLiteral, PLUS);
+ Eclipse.setGeneratedBy(current, source);
+ current = new BinaryExpression(current, ex, PLUS);
+ Eclipse.setGeneratedBy(current, source);
+ }
+ if (!first) {
+ StringLiteral suffixLiteral = new StringLiteral(suffix, pS, pE, 0);
+ Eclipse.setGeneratedBy(suffixLiteral, source);
+ current = new BinaryExpression(current, suffixLiteral, PLUS);
+ Eclipse.setGeneratedBy(current, source);
+ }
+
+ ReturnStatement returnStatement = new ReturnStatement(current, pS, pE);
+ Eclipse.setGeneratedBy(returnStatement, source);
+
+ MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ Eclipse.setGeneratedBy(method, source);
+ method.modifiers = toEclipseModifier(AccessLevel.PUBLIC);
+ method.returnType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_STRING, new long[] {p, p, p});
+ Eclipse.setGeneratedBy(method.returnType, source);
+ method.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)};
+ method.arguments = null;
+ method.selector = "toString".toCharArray();
+ method.thrownExceptions = null;
+ method.typeParameters = null;
+ method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart;
+ method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd;
+ method.statements = new Statement[] { returnStatement };
+ return method;
+ }
+
+ private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ "byte", "short", "int", "long", "char", "boolean", "double", "float")));
+
+ private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long)pS << 32 | pE;
+ NameReference ref;
+ if (varNames.length > 1) ref = new QualifiedNameReference(varNames, new long[varNames.length], pS, pE);
+ else ref = new SingleNameReference(varNames[0], p);
+ Eclipse.setGeneratedBy(ref, source);
+ return ref;
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/package-info.java b/src/core/lombok/eclipse/handlers/package-info.java
new file mode 100644
index 00000000..062b73b3
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Contains the classes that implement the transformations for all of lombok's various features on the eclipse platform.
+ */
+package lombok.eclipse.handlers;
diff --git a/src/core/lombok/eclipse/package-info.java b/src/core/lombok/eclipse/package-info.java
new file mode 100644
index 00000000..0b5add4c
--- /dev/null
+++ b/src/core/lombok/eclipse/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Includes the eclipse-specific implementations of the lombok AST and annotation introspection support.
+ */
+package lombok.eclipse;
diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java
new file mode 100644
index 00000000..bbe9dec0
--- /dev/null
+++ b/src/core/lombok/javac/HandlerLibrary.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+
+import javax.annotation.processing.Messager;
+import javax.tools.Diagnostic;
+
+import lombok.core.PrintAST;
+import lombok.core.SpiLoadUtil;
+import lombok.core.TypeLibrary;
+import lombok.core.TypeResolver;
+import lombok.core.AnnotationValues.AnnotationValueDecodeFail;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+
+/**
+ * This class tracks 'handlers' and knows how to invoke them for any given AST node.
+ *
+ * This class can find the handlers (via SPI discovery) and will set up the given AST node, such as
+ * building an AnnotationValues instance.
+ */
+public class HandlerLibrary {
+ private final TypeLibrary typeLibrary = new TypeLibrary();
+ private final Map<String, AnnotationHandlerContainer<?>> annotationHandlers = new HashMap<String, AnnotationHandlerContainer<?>>();
+ private final Collection<JavacASTVisitor> visitorHandlers = new ArrayList<JavacASTVisitor>();
+ private final Messager messager;
+ private boolean skipPrintAST = true;
+
+ /**
+ * Creates a new HandlerLibrary that will report any problems or errors to the provided messager.
+ * You probably want to use {@link #load(Messager)} instead.
+ */
+ public HandlerLibrary(Messager messager) {
+ this.messager = messager;
+ }
+
+ private static class AnnotationHandlerContainer<T extends Annotation> {
+ private JavacAnnotationHandler<T> handler;
+ private Class<T> annotationClass;
+
+ AnnotationHandlerContainer(JavacAnnotationHandler<T> handler, Class<T> annotationClass) {
+ this.handler = handler;
+ this.annotationClass = annotationClass;
+ }
+
+ public boolean handle(final JavacNode node) {
+ return handler.handle(Javac.createAnnotation(annotationClass, node), (JCAnnotation)node.get(), node);
+ }
+ }
+
+ /**
+ * Creates a new HandlerLibrary that will report any problems or errors to the provided messager,
+ * then uses SPI discovery to load all annotation and visitor based handlers so that future calls
+ * to the handle methods will defer to these handlers.
+ */
+ public static HandlerLibrary load(Messager messager) {
+ HandlerLibrary library = new HandlerLibrary(messager);
+
+ loadAnnotationHandlers(library);
+ loadVisitorHandlers(library);
+
+ return library;
+ }
+
+ /** Uses SPI Discovery to find implementations of {@link JavacAnnotationHandler}. */
+ @SuppressWarnings("unchecked")
+ private static void loadAnnotationHandlers(HandlerLibrary lib) {
+ //No, that seemingly superfluous reference to JavacAnnotationHandler's classloader is not in fact superfluous!
+ Iterator<JavacAnnotationHandler> it = ServiceLoader.load(JavacAnnotationHandler.class,
+ JavacAnnotationHandler.class.getClassLoader()).iterator();
+ while (it.hasNext()) {
+ try {
+ JavacAnnotationHandler<?> handler = it.next();
+ Class<? extends Annotation> annotationClass =
+ SpiLoadUtil.findAnnotationClass(handler.getClass(), JavacAnnotationHandler.class);
+ AnnotationHandlerContainer<?> container = new AnnotationHandlerContainer(handler, annotationClass);
+ if (lib.annotationHandlers.put(container.annotationClass.getName(), container) != null) {
+ lib.javacWarning("Duplicate handlers for annotation type: " + container.annotationClass.getName());
+ }
+ lib.typeLibrary.addType(container.annotationClass.getName());
+ } catch (ServiceConfigurationError e) {
+ lib.javacWarning("Can't load Lombok annotation handler for javac", e);
+ }
+ }
+ }
+
+ /** Uses SPI Discovery to find implementations of {@link JavacASTVisitor}. */
+ private static void loadVisitorHandlers(HandlerLibrary lib) {
+ //No, that seemingly superfluous reference to JavacASTVisitor's classloader is not in fact superfluous!
+ Iterator<JavacASTVisitor> it = ServiceLoader.load(JavacASTVisitor.class,
+ JavacASTVisitor.class.getClassLoader()).iterator();
+ while (it.hasNext()) {
+ try {
+ JavacASTVisitor handler = it.next();
+ lib.visitorHandlers.add(handler);
+ } catch (ServiceConfigurationError e) {
+ lib.javacWarning("Can't load Lombok visitor handler for javac", e);
+ }
+ }
+ }
+
+ /** Generates a warning in the Messager that was used to initialize this HandlerLibrary. */
+ public void javacWarning(String message) {
+ javacWarning(message, null);
+ }
+
+ /** Generates a warning in the Messager that was used to initialize this HandlerLibrary. */
+ public void javacWarning(String message, Throwable t) {
+ messager.printMessage(Diagnostic.Kind.WARNING, message + (t == null ? "" : (": " + t)));
+ }
+
+ /** Generates an error in the Messager that was used to initialize this HandlerLibrary. */
+ public void javacError(String message) {
+ javacError(message, null);
+ }
+
+ /** Generates an error in the Messager that was used to initialize this HandlerLibrary. */
+ public void javacError(String message, Throwable t) {
+ messager.printMessage(Diagnostic.Kind.ERROR, message + (t == null ? "" : (": " + t)));
+ if (t != null) t.printStackTrace();
+ }
+
+ /**
+ * Handles the provided annotation node by first finding a qualifying instance of
+ * {@link JavacAnnotationHandler} and if one exists, calling it with a freshly cooked up
+ * instance of {@link lombok.core.AnnotationValues}.
+ *
+ * Note that depending on the printASTOnly flag, the {@link lombok.core.PrintAST} annotation
+ * will either be silently skipped, or everything that isn't {@code PrintAST} will be skipped.
+ *
+ * The HandlerLibrary will attempt to guess if the given annotation node represents a lombok annotation.
+ * For example, if {@code lombok.*} is in the import list, then this method will guess that
+ * {@code Getter} refers to {@code lombok.Getter}, presuming that {@link lombok.javac.handlers.HandleGetter}
+ * has been loaded.
+ *
+ * @param unit The Compilation Unit that contains the Annotation AST Node.
+ * @param node The Lombok AST Node representing the Annotation AST Node.
+ * @param annotation 'node.get()' - convenience parameter.
+ */
+ public boolean handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation) {
+ TypeResolver resolver = new TypeResolver(typeLibrary, node.getPackageDeclaration(), node.getImportStatements());
+ String rawType = annotation.annotationType.toString();
+ boolean handled = false;
+ for (String fqn : resolver.findTypeMatches(node, rawType)) {
+ boolean isPrintAST = fqn.equals(PrintAST.class.getName());
+ if (isPrintAST == skipPrintAST) continue;
+ AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
+ if (container == null) continue;
+
+ try {
+ handled |= container.handle(node);
+ } catch (AnnotationValueDecodeFail fail) {
+ fail.owner.setError(fail.getMessage(), fail.idx);
+ } catch (Throwable t) {
+ String sourceName = "(unknown).java";
+ if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName();
+ javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t);
+ }
+ }
+
+ return handled;
+ }
+
+ /**
+ * Will call all registered {@link JavacASTVisitor} instances.
+ */
+ public void callASTVisitors(JavacAST ast) {
+ for (JavacASTVisitor visitor : visitorHandlers) try {
+ ast.traverse(visitor);
+ } catch (Throwable t) {
+ javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t);
+ }
+ }
+
+ /**
+ * Lombok does not currently support triggering annotations in a specified order; the order is essentially
+ * random right now. This lack of order is particularly annoying for the {@code PrintAST} annotation,
+ * which is almost always intended to run last. Hence, this hack, which lets it in fact run last.
+ *
+ * @see #skipAllButPrintAST()
+ */
+ public void skipPrintAST() {
+ skipPrintAST = true;
+ }
+
+ /** @see #skipPrintAST() */
+ public void skipAllButPrintAST() {
+ skipPrintAST = false;
+ }
+}
diff --git a/src/core/lombok/javac/Javac.java b/src/core/lombok/javac/Javac.java
new file mode 100644
index 00000000..58a24207
--- /dev/null
+++ b/src/core/lombok/javac/Javac.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import lombok.core.AnnotationValues;
+import lombok.core.TypeLibrary;
+import lombok.core.TypeResolver;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues.AnnotationValue;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+
+/**
+ * Container for static utility methods relevant to lombok's operation on javac.
+ */
+public class Javac {
+ private Javac() {
+ //prevent instantiation
+ }
+
+ /**
+ * Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type.
+ *
+ * @param type An actual annotation type, such as {@code lombok.Getter.class}.
+ * @param node A Lombok AST node representing an annotation in source code.
+ */
+ public static boolean annotationTypeMatches(Class<? extends Annotation> type, JavacNode node) {
+ if (node.getKind() != Kind.ANNOTATION) return false;
+ String typeName = ((JCAnnotation)node.get()).annotationType.toString();
+
+ TypeLibrary library = new TypeLibrary();
+ library.addType(type.getName());
+ TypeResolver resolver = new TypeResolver(library, node.getPackageDeclaration(), node.getImportStatements());
+ Collection<String> typeMatches = resolver.findTypeMatches(node, typeName);
+
+ for (String match : typeMatches) {
+ if (match.equals(type.getName())) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates an instance of {@code AnnotationValues} for the provided AST Node.
+ *
+ * @param type An annotation class type, such as {@code lombok.Getter.class}.
+ * @param node A Lombok AST node representing an annotation in source code.
+ */
+ public static <A extends Annotation> AnnotationValues<A> createAnnotation(Class<A> type, final JavacNode node) {
+ Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>();
+ JCAnnotation anno = (JCAnnotation) node.get();
+ List<JCExpression> arguments = anno.getArguments();
+ for (Method m : type.getDeclaredMethods()) {
+ if (!Modifier.isPublic(m.getModifiers())) continue;
+ String name = m.getName();
+ List<String> raws = new ArrayList<String>();
+ List<Object> guesses = new ArrayList<Object>();
+ final List<DiagnosticPosition> positions = new ArrayList<DiagnosticPosition>();
+ boolean isExplicit = false;
+
+ for (JCExpression arg : arguments) {
+ String mName;
+ JCExpression rhs;
+
+ if (arg instanceof JCAssign) {
+ JCAssign assign = (JCAssign) arg;
+ mName = assign.lhs.toString();
+ rhs = assign.rhs;
+ } else {
+ rhs = arg;
+ mName = "value";
+ }
+
+ if (!mName.equals(name)) continue;
+ isExplicit = true;
+ if (rhs instanceof JCNewArray) {
+ List<JCExpression> elems = ((JCNewArray)rhs).elems;
+ for (JCExpression inner : elems) {
+ raws.add(inner.toString());
+ guesses.add(calculateGuess(inner));
+ positions.add(inner.pos());
+ }
+ } else {
+ raws.add(rhs.toString());
+ guesses.add(calculateGuess(rhs));
+ positions.add(rhs.pos());
+ }
+ }
+
+ values.put(name, new AnnotationValue(node, raws, guesses, isExplicit) {
+ @Override public void setError(String message, int valueIdx) {
+ if (valueIdx < 0) node.addError(message);
+ else node.addError(message, positions.get(valueIdx));
+ }
+ @Override public void setWarning(String message, int valueIdx) {
+ if (valueIdx < 0) node.addWarning(message);
+ else node.addWarning(message, positions.get(valueIdx));
+ }
+ });
+ }
+
+ return new AnnotationValues<A>(type, values, node);
+ }
+
+ /**
+ * Turns an expression into a guessed intended literal. Only works for literals, as you can imagine.
+ *
+ * Will for example turn a TrueLiteral into 'Boolean.valueOf(true)'.
+ */
+ private static Object calculateGuess(JCExpression expr) {
+ if (expr instanceof JCLiteral) {
+ JCLiteral lit = (JCLiteral)expr;
+ if (lit.getKind() == com.sun.source.tree.Tree.Kind.BOOLEAN_LITERAL) {
+ return ((Number)lit.value).intValue() == 0 ? false : true;
+ }
+ return lit.value;
+ } else if (expr instanceof JCIdent || expr instanceof JCFieldAccess) {
+ String x = expr.toString();
+ if (x.endsWith(".class")) x = x.substring(0, x.length() - 6);
+ else {
+ int idx = x.lastIndexOf('.');
+ if (idx > -1) x = x.substring(idx + 1);
+ }
+ return x;
+ } else return null;
+ }
+}
diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java
new file mode 100644
index 00000000..f2c83fb8
--- /dev/null
+++ b/src/core/lombok/javac/JavacAST.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.processing.Messager;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+import lombok.core.AST;
+
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Symtab;
+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.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCImport;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+
+/**
+ * Wraps around javac's internal AST view to add useful features as well as the ability to visit parents from children,
+ * something javac's own AST system does not offer.
+ */
+public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
+ private final Messager messager;
+ private final Name.Table nameTable;
+ private final TreeMaker treeMaker;
+ private final Symtab symtab;
+ private final Log log;
+ private final Context context;
+
+ /**
+ * Creates a new JavacAST of the provided Compilation Unit.
+ *
+ * @param trees The trees instance to use to inspect the compilation unit. Generate via:
+ * {@code Trees.getInstance(env)}
+ * @param messager A Messager for warning and error reporting.
+ * @param context A Context object for interfacing with the compiler.
+ * @param top The compilation unit, which serves as the top level node in the tree to be built.
+ */
+ public JavacAST(Trees trees, Messager messager, Context context, JCCompilationUnit top) {
+ super(top.sourcefile == null ? null : top.sourcefile.toString());
+ setTop(buildCompilationUnit(top));
+ this.context = context;
+ this.messager = messager;
+ this.log = Log.instance(context);
+ this.nameTable = Name.Table.instance(context);
+ this.treeMaker = TreeMaker.instance(context);
+ this.symtab = Symtab.instance(context);
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getPackageDeclaration() {
+ JCCompilationUnit unit = (JCCompilationUnit)top().get();
+ return unit.pid instanceof JCFieldAccess ? unit.pid.toString() : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Collection<String> getImportStatements() {
+ List<String> imports = new ArrayList<String>();
+ JCCompilationUnit unit = (JCCompilationUnit)top().get();
+ for (JCTree def : unit.defs) {
+ if (def instanceof JCImport) {
+ imports.add(((JCImport)def).qualid.toString());
+ }
+ }
+
+ return imports;
+ }
+
+ /**
+ * Runs through the entire AST, starting at the compilation unit, calling the provided visitor's visit methods
+ * for each node, depth first.
+ */
+ public void traverse(JavacASTVisitor visitor) {
+ top().traverse(visitor);
+ }
+
+ void traverseChildren(JavacASTVisitor visitor, JavacNode node) {
+ for (JavacNode child : new ArrayList<JavacNode>(node.down())) {
+ child.traverse(visitor);
+ }
+ }
+
+ /** @return A Name object generated for the proper name table belonging to this AST. */
+ public Name toName(String name) {
+ return nameTable.fromString(name);
+ }
+
+ /** @return A TreeMaker instance that you can use to create new AST nodes. */
+ public TreeMaker getTreeMaker() {
+ return treeMaker;
+ }
+
+ /** @return The symbol table used by this AST for symbols. */
+ public Symtab getSymbolTable() {
+ return symtab;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected JavacNode buildTree(JCTree node, Kind kind) {
+ switch (kind) {
+ case COMPILATION_UNIT:
+ return buildCompilationUnit((JCCompilationUnit) node);
+ case TYPE:
+ return buildType((JCClassDecl) node);
+ case FIELD:
+ return buildField((JCVariableDecl) node);
+ case INITIALIZER:
+ return buildInitializer((JCBlock) node);
+ case METHOD:
+ return buildMethod((JCMethodDecl) node);
+ case ARGUMENT:
+ return buildLocalVar((JCVariableDecl) node, kind);
+ case LOCAL:
+ return buildLocalVar((JCVariableDecl) node, kind);
+ case STATEMENT:
+ return buildStatementOrExpression(node);
+ case ANNOTATION:
+ return buildAnnotation((JCAnnotation) node);
+ default:
+ throw new AssertionError("Did not expect: " + kind);
+ }
+ }
+
+ private JavacNode buildCompilationUnit(JCCompilationUnit top) {
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCTree s : top.defs) {
+ if (s instanceof JCClassDecl) {
+ addIfNotNull(childNodes, buildType((JCClassDecl)s));
+ } // else they are import statements, which we don't care about. Or Skip objects, whatever those are.
+ }
+
+ return new JavacNode(this, top, childNodes, Kind.COMPILATION_UNIT);
+ }
+
+ private JavacNode buildType(JCClassDecl type) {
+ if (setAndGetAsHandled(type)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+
+ for (JCTree def : type.defs) {
+ for (JCAnnotation annotation : type.mods.annotations) addIfNotNull(childNodes, buildAnnotation(annotation));
+ /* A def can be:
+ * JCClassDecl for inner types
+ * JCMethodDecl for constructors and methods
+ * JCVariableDecl for fields
+ * JCBlock for (static) initializers
+ */
+ if (def instanceof JCMethodDecl) addIfNotNull(childNodes, buildMethod((JCMethodDecl)def));
+ else if (def instanceof JCClassDecl) addIfNotNull(childNodes, buildType((JCClassDecl)def));
+ else if (def instanceof JCVariableDecl) addIfNotNull(childNodes, buildField((JCVariableDecl)def));
+ else if (def instanceof JCBlock) addIfNotNull(childNodes, buildInitializer((JCBlock)def));
+ }
+
+ return putInMap(new JavacNode(this, type, childNodes, Kind.TYPE));
+ }
+
+ private JavacNode buildField(JCVariableDecl field) {
+ if (setAndGetAsHandled(field)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCAnnotation annotation : field.mods.annotations) addIfNotNull(childNodes, buildAnnotation(annotation));
+ addIfNotNull(childNodes, buildExpression(field.init));
+ return putInMap(new JavacNode(this, field, childNodes, Kind.FIELD));
+ }
+
+ private JavacNode buildLocalVar(JCVariableDecl local, Kind kind) {
+ if (setAndGetAsHandled(local)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCAnnotation annotation : local.mods.annotations) addIfNotNull(childNodes, buildAnnotation(annotation));
+ addIfNotNull(childNodes, buildExpression(local.init));
+ return putInMap(new JavacNode(this, local, childNodes, kind));
+ }
+
+ private JavacNode buildInitializer(JCBlock initializer) {
+ if (setAndGetAsHandled(initializer)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCStatement statement: initializer.stats) addIfNotNull(childNodes, buildStatement(statement));
+ return putInMap(new JavacNode(this, initializer, childNodes, Kind.INITIALIZER));
+ }
+
+ private JavacNode buildMethod(JCMethodDecl method) {
+ if (setAndGetAsHandled(method)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCAnnotation annotation : method.mods.annotations) addIfNotNull(childNodes, buildAnnotation(annotation));
+ for (JCVariableDecl param : method.params) addIfNotNull(childNodes, buildLocalVar(param, Kind.ARGUMENT));
+ if (method.body != null && method.body.stats != null) {
+ for (JCStatement statement : method.body.stats) addIfNotNull(childNodes, buildStatement(statement));
+ }
+ return putInMap(new JavacNode(this, method, childNodes, Kind.METHOD));
+ }
+
+ private JavacNode buildAnnotation(JCAnnotation annotation) {
+ if (setAndGetAsHandled(annotation)) return null;
+ return putInMap(new JavacNode(this, annotation, null, Kind.ANNOTATION));
+ }
+
+ private JavacNode buildExpression(JCExpression expression) {
+ return buildStatementOrExpression(expression);
+ }
+
+ private JavacNode buildStatement(JCStatement statement) {
+ return buildStatementOrExpression(statement);
+ }
+
+ private JavacNode buildStatementOrExpression(JCTree statement) {
+ if (statement == null) return null;
+ if (statement instanceof JCAnnotation) return null;
+ if (statement instanceof JCClassDecl) return buildType((JCClassDecl)statement);
+ if (statement instanceof JCVariableDecl) return buildLocalVar((JCVariableDecl)statement, Kind.LOCAL);
+
+ if (setAndGetAsHandled(statement)) return null;
+
+ return drill(statement);
+ }
+
+ private JavacNode drill(JCTree statement) {
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa));
+ return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT));
+ }
+
+ /** For javac, both JCExpression and JCStatement are considered as valid children types. */
+ @Override
+ protected Collection<Class<? extends JCTree>> getStatementTypes() {
+ Collection<Class<? extends JCTree>> collection = new ArrayList<Class<? extends JCTree>>(2);
+ collection.add(JCStatement.class);
+ collection.add(JCExpression.class);
+ return collection;
+ }
+
+ private static void addIfNotNull(Collection<JavacNode> nodes, JavacNode node) {
+ if (node != null) nodes.add(node);
+ }
+
+ /** Supply either a position or a node (in that case, position of the node is used) */
+ void printMessage(Diagnostic.Kind kind, String message, JavacNode node, DiagnosticPosition pos) {
+ JavaFileObject oldSource = null;
+ JavaFileObject newSource = null;
+ JCTree astObject = node == null ? null : node.get();
+ JCCompilationUnit top = (JCCompilationUnit) top().get();
+ newSource = top.sourcefile;
+ if (newSource != null) {
+ oldSource = log.useSource(newSource);
+ if (pos == null) pos = astObject.pos();
+ }
+ try {
+ switch (kind) {
+ case ERROR:
+ increaseErrorCount(messager);
+ boolean prev = log.multipleErrors;
+ log.multipleErrors = true;
+ try {
+ log.error(pos, "proc.messager", message);
+ } finally {
+ log.multipleErrors = prev;
+ }
+ break;
+ default:
+ case WARNING:
+ log.warning(pos, "proc.messager", message);
+ break;
+ }
+ } finally {
+ if (oldSource != null) log.useSource(oldSource);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void setElementInASTCollection(Field field, Object refField, List<Collection<?>> chain, Collection<?> collection, int idx, JCTree newN) throws IllegalAccessException {
+ com.sun.tools.javac.util.List<?> list = setElementInConsList(chain, collection, ((List<?>)collection).get(idx), newN);
+ field.set(refField, list);
+ }
+
+ private com.sun.tools.javac.util.List<?> setElementInConsList(List<Collection<?>> chain, Collection<?> current, Object oldO, Object newO) {
+ com.sun.tools.javac.util.List<?> oldL = (com.sun.tools.javac.util.List<?>) current;
+ com.sun.tools.javac.util.List<?> newL = replaceInConsList(oldL, oldO, newO);
+ if (chain.isEmpty()) return newL;
+ List<Collection<?>> reducedChain = new ArrayList<Collection<?>>(chain);
+ Collection<?> newCurrent = reducedChain.remove(reducedChain.size() -1);
+ return setElementInConsList(reducedChain, newCurrent, oldL, newL);
+ }
+
+ private com.sun.tools.javac.util.List<?> replaceInConsList(com.sun.tools.javac.util.List<?> oldL, Object oldO, Object newO) {
+ boolean repl = false;
+ Object[] a = oldL.toArray();
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] == oldO) {
+ a[i] = newO;
+ repl = true;
+ }
+ }
+
+ if (repl) return com.sun.tools.javac.util.List.<Object>from(a);
+ return oldL;
+ }
+
+ private void increaseErrorCount(Messager m) {
+ try {
+ Field f = m.getClass().getDeclaredField("errorCount");
+ f.setAccessible(true);
+ if (f.getType() == int.class) {
+ int val = ((Number)f.get(m)).intValue();
+ f.set(m, val +1);
+ }
+ } catch (Throwable t) {
+ //Very unfortunate, but in most cases it still works fine, so we'll silently swallow it.
+ }
+ }
+}
diff --git a/src/core/lombok/javac/JavacASTAdapter.java b/src/core/lombok/javac/JavacASTAdapter.java
new file mode 100644
index 00000000..41bc46d3
--- /dev/null
+++ b/src/core/lombok/javac/JavacASTAdapter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import com.sun.tools.javac.tree.JCTree;
+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.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+/**
+ * Standard adapter for the {@link JavacASTVisitor} interface. Every method on that interface
+ * has been implemented with an empty body. Override whichever methods you need.
+ */
+public class JavacASTAdapter implements JavacASTVisitor {
+ /** {@inheritDoc} */
+ @Override public void visitCompilationUnit(JavacNode top, JCCompilationUnit unit) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitCompilationUnit(JavacNode top, JCCompilationUnit unit) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitType(JavacNode typeNode, JCClassDecl type) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitType(JavacNode typeNode, JCClassDecl type) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitField(JavacNode fieldNode, JCVariableDecl field) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitAnnotationOnField(JCVariableDecl field, JavacNode annotationNode, JCAnnotation annotation) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitField(JavacNode fieldNode, JCVariableDecl field) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitInitializer(JavacNode initializerNode, JCBlock initializer) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitInitializer(JavacNode initializerNode, JCBlock initializer) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitMethod(JavacNode methodNode, JCMethodDecl method) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitAnnotationOnMethod(JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitMethod(JavacNode methodNode, JCMethodDecl method) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitMethodArgument(JavacNode argumentNode, JCVariableDecl argument, JCMethodDecl method) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) {}
+ /** {@inheritDoc} */
+ @Override public void endVisitMethodArgument(JavacNode argumentNode, JCVariableDecl argument, JCMethodDecl method) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode annotationNode, JCAnnotation annotation) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitLocal(JavacNode localNode, JCVariableDecl local) {}
+
+ /** {@inheritDoc} */
+ @Override public void visitStatement(JavacNode statementNode, JCTree statement) {}
+
+ /** {@inheritDoc} */
+ @Override public void endVisitStatement(JavacNode statementNode, JCTree statement) {}
+}
diff --git a/src/core/lombok/javac/JavacASTVisitor.java b/src/core/lombok/javac/JavacASTVisitor.java
new file mode 100644
index 00000000..3c5887a7
--- /dev/null
+++ b/src/core/lombok/javac/JavacASTVisitor.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.io.PrintStream;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
+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.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+/**
+ * Implement so you can ask any JavacAST.LombokNode to traverse depth-first through all children,
+ * calling the appropriate visit and endVisit methods.
+ */
+public interface JavacASTVisitor {
+ /**
+ * Called at the very beginning and end.
+ */
+ void visitCompilationUnit(JavacNode top, JCCompilationUnit unit);
+ void endVisitCompilationUnit(JavacNode top, JCCompilationUnit unit);
+
+ /**
+ * Called when visiting a type (a class, interface, annotation, enum, etcetera).
+ */
+ void visitType(JavacNode typeNode, JCClassDecl type);
+ void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation);
+ void endVisitType(JavacNode typeNode, JCClassDecl type);
+
+ /**
+ * Called when visiting a field of a class.
+ */
+ void visitField(JavacNode fieldNode, JCVariableDecl field);
+ void visitAnnotationOnField(JCVariableDecl field, JavacNode annotationNode, JCAnnotation annotation);
+ void endVisitField(JavacNode fieldNode, JCVariableDecl field);
+
+ /**
+ * Called for static and instance initializers. You can tell the difference via the isStatic() method.
+ */
+ void visitInitializer(JavacNode initializerNode, JCBlock initializer);
+ void endVisitInitializer(JavacNode initializerNode, JCBlock initializer);
+
+ /**
+ * Called for both methods and constructors.
+ */
+ void visitMethod(JavacNode methodNode, JCMethodDecl method);
+ void visitAnnotationOnMethod(JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation);
+ void endVisitMethod(JavacNode methodNode, JCMethodDecl method);
+
+ /**
+ * Visits a method argument.
+ */
+ void visitMethodArgument(JavacNode argumentNode, JCVariableDecl argument, JCMethodDecl method);
+ void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation);
+ void endVisitMethodArgument(JavacNode argumentNode, JCVariableDecl argument, JCMethodDecl method);
+
+ /**
+ * Visits a local declaration - that is, something like 'int x = 10;' on the method level. Also called
+ * for method parameters.
+ */
+ void visitLocal(JavacNode localNode, JCVariableDecl local);
+ void visitAnnotationOnLocal(JCVariableDecl local, JavacNode annotationNode, JCAnnotation annotation);
+ void endVisitLocal(JavacNode localNode, JCVariableDecl local);
+
+ /**
+ * Visits a statement that isn't any of the other visit methods (e.g. JCClassDecl).
+ * The statement object is guaranteed to be either a JCStatement or a JCExpression.
+ */
+ void visitStatement(JavacNode statementNode, JCTree statement);
+ void endVisitStatement(JavacNode statementNode, JCTree statement);
+
+ /**
+ * Prints the structure of an AST.
+ */
+ public static class Printer implements JavacASTVisitor {
+ private final PrintStream out;
+ private final boolean printContent;
+ private int disablePrinting = 0;
+ private int indent = 0;
+
+ /**
+ * @param printContent if true, bodies are printed directly, as java code,
+ * instead of a tree listing of every AST node inside it.
+ */
+ public Printer(boolean printContent) {
+ this(printContent, System.out);
+ }
+
+ /**
+ * @param printContent if true, bodies are printed directly, as java code,
+ * instead of a tree listing of every AST node inside it.
+ * @param out write output to this stream. You must close it yourself. flush() is called after every line.
+ *
+ * @see java.io.PrintStream#flush()
+ */
+ public Printer(boolean printContent, PrintStream out) {
+ this.printContent = printContent;
+ this.out = out;
+ }
+
+ private void forcePrint(String text, Object... params) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) sb.append(" ");
+ out.printf(sb.append(text).append('\n').toString(), params);
+ out.flush();
+ }
+
+ private void print(String text, Object... params) {
+ if (disablePrinting == 0) forcePrint(text, params);
+ }
+
+ @Override public void visitCompilationUnit(JavacNode LombokNode, JCCompilationUnit unit) {
+ out.println("---------------------------------------------------------");
+
+ print("<CU %s>", LombokNode.getFileName());
+ indent++;
+ }
+
+ @Override public void endVisitCompilationUnit(JavacNode node, JCCompilationUnit unit) {
+ indent--;
+ print("</CUD>");
+ }
+
+ @Override public void visitType(JavacNode node, JCClassDecl type) {
+ print("<TYPE %s>", type.name);
+ indent++;
+ if (printContent) {
+ print("%s", type);
+ disablePrinting++;
+ }
+ }
+
+ @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode node, JCAnnotation annotation) {
+ forcePrint("<ANNOTATION: %s />", annotation);
+ }
+
+ @Override public void endVisitType(JavacNode node, JCClassDecl type) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</TYPE %s>", type.name);
+ }
+
+ @Override public void visitInitializer(JavacNode node, JCBlock initializer) {
+ print("<%s INITIALIZER>",
+ initializer.isStatic() ? "static" : "instance");
+ indent++;
+ if (printContent) {
+ print("%s", initializer);
+ disablePrinting++;
+ }
+ }
+
+ @Override public void endVisitInitializer(JavacNode node, JCBlock initializer) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</%s INITIALIZER>", initializer.isStatic() ? "static" : "instance");
+ }
+
+ @Override public void visitField(JavacNode node, JCVariableDecl field) {
+ print("<FIELD %s %s>", field.vartype, field.name);
+ indent++;
+ if (printContent) {
+ if (field.init != null) print("%s", field.init);
+ disablePrinting++;
+ }
+ }
+
+ @Override public void visitAnnotationOnField(JCVariableDecl field, JavacNode node, JCAnnotation annotation) {
+ forcePrint("<ANNOTATION: %s />", annotation);
+ }
+
+ @Override public void endVisitField(JavacNode node, JCVariableDecl field) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</FIELD %s %s>", field.vartype, field.name);
+ }
+
+ @Override public void visitMethod(JavacNode node, JCMethodDecl method) {
+ final String type;
+ if (method.name.contentEquals("<init>")) {
+ if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) {
+ type = "DEFAULTCONSTRUCTOR";
+ } else type = "CONSTRUCTOR";
+ } else type = "METHOD";
+ print("<%s %s> returns: %s", type, method.name, method.restype);
+ indent++;
+ if (printContent) {
+ if (method.body == null) print("(ABSTRACT)");
+ else print("%s", method.body);
+ disablePrinting++;
+ }
+ }
+
+ @Override public void visitAnnotationOnMethod(JCMethodDecl method, JavacNode node, JCAnnotation annotation) {
+ forcePrint("<ANNOTATION: %s />", annotation);
+ }
+
+ @Override public void endVisitMethod(JavacNode node, JCMethodDecl method) {
+ if (printContent) disablePrinting--;
+ indent--;
+ print("</%s %s>", "XMETHOD", method.name);
+ }
+
+ @Override public void visitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) {
+ print("<METHODARG %s %s>", arg.vartype, arg.name);
+ indent++;
+ }
+
+ @Override public void visitAnnotationOnMethodArgument(JCVariableDecl arg, JCMethodDecl method, JavacNode nodeAnnotation, JCAnnotation annotation) {
+ forcePrint("<ANNOTATION: %s />", annotation);
+ }
+
+ @Override public void endVisitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) {
+ indent--;
+ print("</METHODARG %s %s>", arg.vartype, arg.name);
+ }
+
+ @Override public void visitLocal(JavacNode node, JCVariableDecl local) {
+ print("<LOCAL %s %s>", local.vartype, local.name);
+ indent++;
+ }
+
+ @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode node, JCAnnotation annotation) {
+ print("<ANNOTATION: %s />", annotation);
+ }
+
+ @Override public void endVisitLocal(JavacNode node, JCVariableDecl local) {
+ indent--;
+ print("</LOCAL %s %s>", local.vartype, local.name);
+ }
+
+ @Override public void visitStatement(JavacNode node, JCTree statement) {
+ print("<%s>", statement.getClass());
+ indent++;
+ print("%s", statement);
+ }
+
+ @Override public void endVisitStatement(JavacNode node, JCTree statement) {
+ indent--;
+ print("</%s>", statement.getClass());
+ }
+ }
+}
diff --git a/src/core/lombok/javac/JavacAnnotationHandler.java b/src/core/lombok/javac/JavacAnnotationHandler.java
new file mode 100644
index 00000000..5b6fe4ce
--- /dev/null
+++ b/src/core/lombok/javac/JavacAnnotationHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.lang.annotation.Annotation;
+
+import lombok.core.AnnotationValues;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+
+/**
+ * Implement this interface if you want to be triggered for a specific annotation.
+ *
+ * You MUST replace 'T' with a specific annotation type, such as:
+ *
+ * {@code public class HandleGetter implements JavacAnnotationHandler<Getter>}
+ *
+ * Because this generics parameter is inspected to figure out which class you're interested in.
+ *
+ * You also need to register yourself via SPI discovery as being an implementation of {@code JavacAnnotationHandler}.
+ */
+public interface JavacAnnotationHandler<T extends Annotation> {
+ /**
+ * Called when an annotation is found that is likely to match the annotation you're interested in.
+ *
+ * Be aware that you'll be called for ANY annotation node in the source that looks like a match. There is,
+ * for example, no guarantee that the annotation node belongs to a method, even if you set your
+ * TargetType in the annotation to methods only.
+ *
+ * @param annotation The actual annotation - use this object to retrieve the annotation parameters.
+ * @param ast The javac AST node representing the annotation.
+ * @param annotationNode The Lombok AST wrapper around the 'ast' parameter. You can use this object
+ * to travel back up the chain (something javac AST can't do) to the parent of the annotation, as well
+ * as access useful methods such as generating warnings or errors focused on the annotation.
+ * @return {@code true} if you don't want to be called again about this annotation during this
+ * compile session (you've handled it), or {@code false} to indicate you aren't done yet.
+ */
+ boolean handle(AnnotationValues<T> annotation, JCAnnotation ast, JavacNode annotationNode);
+}
diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java
new file mode 100644
index 00000000..a0ee2789
--- /dev/null
+++ b/src/core/lombok/javac/JavacNode.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac;
+
+import java.util.List;
+
+import javax.tools.Diagnostic;
+
+import lombok.core.AST.Kind;
+
+import com.sun.tools.javac.code.Symtab;
+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.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+
+/**
+ * Javac specific version of the LombokNode class.
+ */
+public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTree> {
+ /**
+ * Passes through to the parent constructor.
+ */
+ public JavacNode(JavacAST ast, JCTree node, List<JavacNode> children, Kind kind) {
+ super(ast, node, children, kind);
+ }
+
+ /**
+ * Visits this node and all child nodes depth-first, calling the provided visitor's visit methods.
+ */
+ public void traverse(JavacASTVisitor visitor) {
+ switch (this.getKind()) {
+ case COMPILATION_UNIT:
+ visitor.visitCompilationUnit(this, (JCCompilationUnit)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitCompilationUnit(this, (JCCompilationUnit)get());
+ break;
+ case TYPE:
+ visitor.visitType(this, (JCClassDecl)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitType(this, (JCClassDecl)get());
+ break;
+ case FIELD:
+ visitor.visitField(this, (JCVariableDecl)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitField(this, (JCVariableDecl)get());
+ break;
+ case METHOD:
+ visitor.visitMethod(this, (JCMethodDecl)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitMethod(this, (JCMethodDecl)get());
+ break;
+ case INITIALIZER:
+ visitor.visitInitializer(this, (JCBlock)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitInitializer(this, (JCBlock)get());
+ break;
+ case ARGUMENT:
+ JCMethodDecl parentMethod = (JCMethodDecl) up().get();
+ visitor.visitMethodArgument(this, (JCVariableDecl)get(), parentMethod);
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitMethodArgument(this, (JCVariableDecl)get(), parentMethod);
+ break;
+ case LOCAL:
+ visitor.visitLocal(this, (JCVariableDecl)get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitLocal(this, (JCVariableDecl)get());
+ break;
+ case STATEMENT:
+ visitor.visitStatement(this, get());
+ ast.traverseChildren(visitor, this);
+ visitor.endVisitStatement(this, get());
+ break;
+ case ANNOTATION:
+ switch (up().getKind()) {
+ case TYPE:
+ visitor.visitAnnotationOnType((JCClassDecl)up().get(), this, (JCAnnotation)get());
+ break;
+ case FIELD:
+ visitor.visitAnnotationOnField((JCVariableDecl)up().get(), this, (JCAnnotation)get());
+ break;
+ case METHOD:
+ visitor.visitAnnotationOnMethod((JCMethodDecl)up().get(), this, (JCAnnotation)get());
+ break;
+ case ARGUMENT:
+ JCVariableDecl argument = (JCVariableDecl)up().get();
+ JCMethodDecl method = (JCMethodDecl)up().up().get();
+ visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation)get());
+ break;
+ case LOCAL:
+ visitor.visitAnnotationOnLocal((JCVariableDecl)up().get(), this, (JCAnnotation)get());
+ break;
+ default:
+ throw new AssertionError("Annotion not expected as child of a " + up().getKind());
+ }
+ break;
+ default:
+ throw new AssertionError("Unexpected kind during node traversal: " + getKind());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getName() {
+ final Name n;
+
+ if (node instanceof JCClassDecl) n = ((JCClassDecl)node).name;
+ else if (node instanceof JCMethodDecl) n = ((JCMethodDecl)node).name;
+ else if (node instanceof JCVariableDecl) n = ((JCVariableDecl)node).name;
+ else n = null;
+
+ return n == null ? null : n.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected boolean calculateIsStructurallySignificant() {
+ if (node instanceof JCClassDecl) return true;
+ if (node instanceof JCMethodDecl) return true;
+ if (node instanceof JCVariableDecl) return true;
+ if (node instanceof JCCompilationUnit) return true;
+ return false;
+ }
+
+ /**
+ * Convenient shortcut to the owning JavacAST object's getTreeMaker method.
+ *
+ * @see JavacAST#getTreeMaker()
+ */
+ public TreeMaker getTreeMaker() {
+ return ast.getTreeMaker();
+ }
+
+ /**
+ * Convenient shortcut to the owning JavacAST object's getSymbolTable method.
+ *
+ * @see JavacAST#getSymbolTable()
+ */
+ public Symtab getSymbolTable() {
+ return ast.getSymbolTable();
+ }
+
+ /**
+ * Convenient shortcut to the owning JavacAST object's getContext method.
+ *
+ * @see JavacAST#getContext()
+ */
+ public Context getContext() {
+ return ast.getContext();
+ }
+
+ /**
+ * Convenient shortcut to the owning JavacAST object's toName method.
+ *
+ * @see JavacAST#toName(String)
+ */
+ public Name toName(String name) {
+ return ast.toName(name);
+ }
+
+ /**
+ * Generates an compiler error focused on the AST node represented by this node object.
+ */
+ @Override public void addError(String message) {
+ ast.printMessage(Diagnostic.Kind.ERROR, message, this, null);
+ }
+
+ /**
+ * Generates an compiler error focused on the AST node represented by this node object.
+ */
+ public void addError(String message, DiagnosticPosition pos) {
+ ast.printMessage(Diagnostic.Kind.ERROR, message, null, pos);
+ }
+
+ /**
+ * Generates a compiler warning focused on the AST node represented by this node object.
+ */
+ @Override public void addWarning(String message) {
+ ast.printMessage(Diagnostic.Kind.WARNING, message, this, null);
+ }
+
+ /**
+ * Generates a compiler warning focused on the AST node represented by this node object.
+ */
+ public void addWarning(String message, DiagnosticPosition pos) {
+ ast.printMessage(Diagnostic.Kind.WARNING, message, null, pos);
+ }
+}
diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java
new file mode 100644
index 00000000..9c851762
--- /dev/null
+++ b/src/core/lombok/javac/apt/Processor.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.apt;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+
+import lombok.javac.HandlerLibrary;
+import lombok.javac.JavacAST;
+import lombok.javac.JavacASTAdapter;
+import lombok.javac.JavacNode;
+
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+
+/**
+ * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler.
+ *
+ * Due to lots of changes in the core javac code, as well as lombok's heavy usage of non-public API, this
+ * code only works for the javac v1.6 compiler; it definitely won't work for javac v1.5, and it probably
+ * won't work for javac v1.7 without modifications.
+ *
+ * To actually enable lombok in a javac compilation run, this class should be in the classpath when
+ * running javac; that's the only requirement.
+ */
+@SupportedAnnotationTypes("*")
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+public class Processor extends AbstractProcessor {
+ private ProcessingEnvironment rawProcessingEnv;
+ private JavacProcessingEnvironment processingEnv;
+ private HandlerLibrary handlers;
+ private Trees trees;
+ private String errorToShow;
+
+ /** {@inheritDoc} */
+ @Override public void init(ProcessingEnvironment procEnv) {
+ super.init(procEnv);
+ this.rawProcessingEnv = procEnv;
+ String className = procEnv.getClass().getName();
+ if (className.startsWith("org.eclipse.jdt.")) {
+ errorToShow = "You should not install lombok.jar as an annotation processor in eclipse. Instead, run lombok.jar as a java application and follow the instructions.";
+ procEnv.getMessager().printMessage(Kind.WARNING, errorToShow);
+ this.processingEnv = null;
+ } else if (!procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) {
+ procEnv.getMessager().printMessage(Kind.WARNING, "You aren't using a compiler based around javac v1.6, so lombok will not work properly.\n" +
+ "Your processor class is: " + className);
+ this.processingEnv = null;
+ this.errorToShow = null;
+ } else {
+ this.processingEnv = (JavacProcessingEnvironment) procEnv;
+ handlers = HandlerLibrary.load(procEnv.getMessager());
+ trees = Trees.instance(procEnv);
+ this.errorToShow = null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (processingEnv == null) {
+ if (errorToShow != null) {
+ Set<? extends Element> rootElements = roundEnv.getRootElements();
+ if (!rootElements.isEmpty()) {
+ rawProcessingEnv.getMessager().printMessage(Kind.WARNING, errorToShow, rootElements.iterator().next());
+ errorToShow = null;
+ }
+ }
+ return false;
+ }
+
+ IdentityHashMap<JCCompilationUnit, Void> units = new IdentityHashMap<JCCompilationUnit, Void>();
+ for (Element element : roundEnv.getRootElements()) {
+ JCCompilationUnit unit = toUnit(element);
+ if (unit != null) units.put(unit, null);
+ }
+
+ List<JavacAST> asts = new ArrayList<JavacAST>();
+
+ for (JCCompilationUnit unit : units.keySet()) asts.add(
+ new JavacAST(trees, processingEnv.getMessager(), processingEnv.getContext(), unit));
+
+ handlers.skipPrintAST();
+ for (JavacAST ast : asts) {
+ ast.traverse(new AnnotationVisitor());
+ handlers.callASTVisitors(ast);
+ }
+
+ handlers.skipAllButPrintAST();
+ for (JavacAST ast : asts) {
+ ast.traverse(new AnnotationVisitor());
+ }
+ return false;
+ }
+
+ private class AnnotationVisitor extends JavacASTAdapter {
+ @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation) {
+ if (annotationNode.isHandled()) return;
+ JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
+ boolean handled = handlers.handleAnnotation(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnField(JCVariableDecl field, JavacNode annotationNode, JCAnnotation annotation) {
+ if (annotationNode.isHandled()) return;
+ JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
+ boolean handled = handlers.handleAnnotation(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnMethod(JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) {
+ if (annotationNode.isHandled()) return;
+ JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
+ boolean handled = handlers.handleAnnotation(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) {
+ if (annotationNode.isHandled()) return;
+ JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
+ boolean handled = handlers.handleAnnotation(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+
+ @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode annotationNode, JCAnnotation annotation) {
+ if (annotationNode.isHandled()) return;
+ JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();
+ boolean handled = handlers.handleAnnotation(top, annotationNode, annotation);
+ if (handled) annotationNode.setHandled();
+ }
+ }
+
+ private JCCompilationUnit toUnit(Element element) {
+ TreePath path = trees == null ? null : trees.getPath(element);
+ if (path == null) return null;
+
+ return (JCCompilationUnit) path.getCompilationUnit();
+ }
+}
diff --git a/src/core/lombok/javac/apt/package-info.java b/src/core/lombok/javac/apt/package-info.java
new file mode 100644
index 00000000..0c47c40f
--- /dev/null
+++ b/src/core/lombok/javac/apt/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Contains the mechanism that instruments javac as an annotation processor.
+ */
+package lombok.javac.apt;
diff --git a/src/core/lombok/javac/handlers/HandleCleanup.java b/src/core/lombok/javac/handlers/HandleCleanup.java
new file mode 100644
index 00000000..88a8e1d7
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleCleanup.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import lombok.Cleanup;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+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.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCCase;
+import com.sun.tools.javac.tree.JCTree.JCCatch;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeCast;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+
+/**
+ * Handles the {@code lombok.Cleanup} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleCleanup implements JavacAnnotationHandler<Cleanup> {
+ @Override public boolean handle(AnnotationValues<Cleanup> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ String cleanupName = annotation.getInstance().value();
+ if (cleanupName.length() == 0) {
+ annotationNode.addError("cleanupName cannot be the empty string.");
+ return true;
+ }
+
+ if (annotationNode.up().getKind() != Kind.LOCAL) {
+ annotationNode.addError("@Cleanup is legal only on local variable declarations.");
+ return true;
+ }
+
+ JCVariableDecl decl = (JCVariableDecl)annotationNode.up().get();
+
+ if (decl.init == null) {
+ annotationNode.addError("@Cleanup variable declarations need to be initialized.");
+ return true;
+ }
+
+ JavacNode ancestor = annotationNode.up().directUp();
+ JCTree blockNode = ancestor.get();
+
+ final List<JCStatement> statements;
+ if (blockNode instanceof JCBlock) {
+ statements = ((JCBlock)blockNode).stats;
+ } else if (blockNode instanceof JCCase) {
+ statements = ((JCCase)blockNode).stats;
+ } else if (blockNode instanceof JCMethodDecl) {
+ statements = ((JCMethodDecl)blockNode).body.stats;
+ } else {
+ annotationNode.addError("@Cleanup is legal only on a local variable declaration inside a block.");
+ return true;
+ }
+
+ boolean seenDeclaration = false;
+ List<JCStatement> tryBlock = List.nil();
+ List<JCStatement> newStatements = List.nil();
+ for (JCStatement statement : statements) {
+ if (!seenDeclaration) {
+ if (statement == decl) seenDeclaration = true;
+ newStatements = newStatements.append(statement);
+ } else {
+ tryBlock = tryBlock.append(statement);
+ }
+ }
+
+ if (!seenDeclaration) {
+ annotationNode.addError("LOMBOK BUG: Can't find this local variable declaration inside its parent.");
+ return true;
+ }
+
+ doAssignmentCheck(annotationNode, tryBlock, decl.name);
+
+ TreeMaker maker = annotationNode.getTreeMaker();
+ JCFieldAccess cleanupCall = maker.Select(maker.Ident(decl.name), annotationNode.toName(cleanupName));
+ List<JCStatement> finalizerBlock = List.<JCStatement>of(maker.Exec(
+ maker.Apply(List.<JCExpression>nil(), cleanupCall, List.<JCExpression>nil())));
+
+ JCBlock finalizer = maker.Block(0, finalizerBlock);
+ newStatements = newStatements.append(maker.Try(maker.Block(0, tryBlock), List.<JCCatch>nil(), finalizer));
+
+ if (blockNode instanceof JCBlock) {
+ ((JCBlock)blockNode).stats = newStatements;
+ } else if (blockNode instanceof JCCase) {
+ ((JCCase)blockNode).stats = newStatements;
+ } else if (blockNode instanceof JCMethodDecl) {
+ ((JCMethodDecl)blockNode).body.stats = newStatements;
+ } else throw new AssertionError("Should not get here");
+
+ ancestor.rebuild();
+
+ return true;
+ }
+
+ private void doAssignmentCheck(JavacNode node, List<JCStatement> statements, Name name) {
+ for (JCStatement statement : statements) doAssignmentCheck0(node, statement, name);
+ }
+
+ private void doAssignmentCheck0(JavacNode node, JCTree statement, Name name) {
+ if (statement instanceof JCAssign) doAssignmentCheck0(node, ((JCAssign)statement).rhs, name);
+ if (statement instanceof JCExpressionStatement) doAssignmentCheck0(node,
+ ((JCExpressionStatement)statement).expr, name);
+ if (statement instanceof JCVariableDecl) doAssignmentCheck0(node, ((JCVariableDecl)statement).init, name);
+ if (statement instanceof JCTypeCast) doAssignmentCheck0(node, ((JCTypeCast)statement).expr, name);
+ if (statement instanceof JCIdent) {
+ if (((JCIdent)statement).name.contentEquals(name)) {
+ JavacNode problemNode = node.getNodeFor(statement);
+ if (problemNode != null) problemNode.addWarning(
+ "You're assigning an auto-cleanup variable to something else. This is a bad idea.");
+ }
+ }
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java
new file mode 100644
index 00000000..eef7f78d
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleData.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import java.lang.reflect.Modifier;
+
+import lombok.Data;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCAssign;
+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.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCReturn;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+
+/**
+ * Handles the {@code lombok.Data} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleData implements JavacAnnotationHandler<Data> {
+ @Override public boolean handle(AnnotationValues<Data> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ JavacNode typeNode = annotationNode.up();
+ JCClassDecl typeDecl = null;
+ if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl)typeNode.get();
+ long flags = typeDecl == null ? 0 : typeDecl.mods.flags;
+ boolean notAClass = (flags & (Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION)) != 0;
+
+ if (typeDecl == null || notAClass) {
+ annotationNode.addError("@Data is only supported on a class.");
+ return false;
+ }
+
+ List<JavacNode> nodesForConstructor = List.nil();
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ //Skip fields that start with $
+ if (fieldDecl.name.toString().startsWith("$")) continue;
+ long fieldFlags = fieldDecl.mods.flags;
+ //Skip static fields.
+ if ((fieldFlags & Flags.STATIC) != 0) continue;
+ boolean isFinal = (fieldFlags & Flags.FINAL) != 0;
+ boolean isNonNull = !findAnnotations(child, TransformationsUtil.NON_NULL_PATTERN).isEmpty();
+ if ((isFinal || isNonNull) && fieldDecl.init == null) nodesForConstructor = nodesForConstructor.append(child);
+ new HandleGetter().generateGetterForField(child, annotationNode.get());
+ if (!isFinal) new HandleSetter().generateSetterForField(child, annotationNode.get());
+ }
+
+ new HandleToString().generateToStringForType(typeNode, annotationNode);
+ new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
+
+ String staticConstructorName = annotation.getInstance().staticConstructor();
+
+ if (constructorExists(typeNode) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl constructor = createConstructor(staticConstructorName.equals(""), typeNode, nodesForConstructor);
+ injectMethod(typeNode, constructor);
+ }
+
+ if (!staticConstructorName.isEmpty() && methodExists("of", typeNode) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl staticConstructor = createStaticConstructor(staticConstructorName, typeNode, nodesForConstructor);
+ injectMethod(typeNode, staticConstructor);
+ }
+
+ return true;
+ }
+
+ private JCMethodDecl createConstructor(boolean isPublic, JavacNode typeNode, List<JavacNode> fields) {
+ TreeMaker maker = typeNode.getTreeMaker();
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ List<JCStatement> nullChecks = List.nil();
+ List<JCStatement> assigns = List.nil();
+ List<JCVariableDecl> params = List.nil();
+
+ for (JavacNode fieldNode : fields) {
+ JCVariableDecl field = (JCVariableDecl) fieldNode.get();
+ List<JCAnnotation> nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN);
+ List<JCAnnotation> nullables = findAnnotations(fieldNode, TransformationsUtil.NULLABLE_PATTERN);
+ JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), field.name, field.vartype, null);
+ params = params.append(param);
+ JCFieldAccess thisX = maker.Select(maker.Ident(fieldNode.toName("this")), field.name);
+ JCAssign assign = maker.Assign(thisX, maker.Ident(field.name));
+ assigns = assigns.append(maker.Exec(assign));
+
+ if (!nonNulls.isEmpty()) {
+ JCStatement nullCheck = generateNullCheck(maker, fieldNode);
+ if (nullCheck != null) nullChecks = nullChecks.append(nullCheck);
+ }
+ }
+
+ JCModifiers mods = maker.Modifiers(isPublic ? Modifier.PUBLIC : Modifier.PRIVATE);
+ return maker.MethodDef(mods, typeNode.toName("<init>"),
+ null, type.typarams, params, List.<JCExpression>nil(), maker.Block(0L, nullChecks.appendList(assigns)), null);
+ }
+
+ private JCMethodDecl createStaticConstructor(String name, JavacNode typeNode, List<JavacNode> fields) {
+ TreeMaker maker = typeNode.getTreeMaker();
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PUBLIC);
+
+ JCExpression returnType, constructorType;
+
+ List<JCTypeParameter> typeParams = List.nil();
+ List<JCVariableDecl> params = List.nil();
+ List<JCExpression> typeArgs1 = List.nil();
+ List<JCExpression> typeArgs2 = List.nil();
+ List<JCExpression> args = List.nil();
+
+ if (!type.typarams.isEmpty()) {
+ for (JCTypeParameter param : type.typarams) {
+ typeArgs1 = typeArgs1.append(maker.Ident(param.name));
+ typeArgs2 = typeArgs2.append(maker.Ident(param.name));
+ typeParams = typeParams.append(maker.TypeParameter(param.name, param.bounds));
+ }
+ returnType = maker.TypeApply(maker.Ident(type.name), typeArgs1);
+ constructorType = maker.TypeApply(maker.Ident(type.name), typeArgs2);
+ } else {
+ returnType = maker.Ident(type.name);
+ constructorType = maker.Ident(type.name);
+ }
+
+ for (JavacNode fieldNode : fields) {
+ JCVariableDecl field = (JCVariableDecl) fieldNode.get();
+ JCExpression pType;
+ if (field.vartype instanceof JCIdent) pType = maker.Ident(((JCIdent)field.vartype).name);
+ else if (field.vartype instanceof JCTypeApply) {
+ JCTypeApply typeApply = (JCTypeApply) field.vartype;
+ List<JCExpression> tArgs = List.nil();
+ for (JCExpression arg : typeApply.arguments) tArgs = tArgs.append(arg);
+ pType = maker.TypeApply(typeApply.clazz, tArgs);
+ } else {
+ pType = field.vartype;
+ }
+ List<JCAnnotation> nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN);
+ List<JCAnnotation> nullables = findAnnotations(fieldNode, TransformationsUtil.NULLABLE_PATTERN);
+ JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), field.name, pType, null);
+ params = params.append(param);
+ args = args.append(maker.Ident(field.name));
+ }
+ JCReturn returnStatement = maker.Return(maker.NewClass(null, List.<JCExpression>nil(), constructorType, args, null));
+ JCBlock body = maker.Block(0, List.<JCStatement>of(returnStatement));
+
+ return maker.MethodDef(mods, typeNode.toName(name), returnType, typeParams, params, List.<JCExpression>nil(), body, null);
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
new file mode 100644
index 00000000..61a4ef63
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+import lombok.EqualsAndHashCode;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.javac.Javac;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.BoundKind;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.TypeTags;
+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.JCArrayTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCBinary;
+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.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCUnary;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+
+/**
+ * Handles the {@code lombok.EqualsAndHashCode} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAndHashCode> {
+ private void checkForBogusFieldNames(JavacNode type, AnnotationValues<EqualsAndHashCode> annotation) {
+ if (annotation.isExplicit("exclude")) {
+ for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, true)) {
+ annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
+ }
+ }
+ if (annotation.isExplicit("of")) {
+ for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().of()), type, false, false)) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
+ }
+
+ @Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ EqualsAndHashCode ann = annotation.getInstance();
+ List<String> excludes = List.from(ann.exclude());
+ List<String> includes = List.from(ann.of());
+ JavacNode typeNode = annotationNode.up();
+
+ checkForBogusFieldNames(typeNode, annotation);
+
+ Boolean callSuper = ann.callSuper();
+ if (!annotation.isExplicit("callSuper")) callSuper = null;
+ if (!annotation.isExplicit("exclude")) excludes = null;
+ if (!annotation.isExplicit("of")) includes = null;
+
+ if (excludes != null && includes != null) {
+ excludes = null;
+ annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored.");
+ }
+
+ return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true);
+ }
+
+ public void generateEqualsAndHashCodeForType(JavacNode typeNode, JavacNode errorNode) {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Javac.annotationTypeMatches(EqualsAndHashCode.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ generateMethods(typeNode, errorNode, null, null, null, false);
+ }
+
+ private boolean generateMethods(JavacNode typeNode, JavacNode errorNode, List<String> excludes, List<String> includes,
+ Boolean callSuper, boolean whineIfExists) {
+ boolean notAClass = true;
+ if (typeNode.get() instanceof JCClassDecl) {
+ long flags = ((JCClassDecl)typeNode.get()).mods.flags;
+ notAClass = (flags & (Flags.INTERFACE | Flags.ANNOTATION | Flags.ENUM)) != 0;
+ }
+
+ if (notAClass) {
+ errorNode.addError("@EqualsAndHashCode is only supported on a class.");
+ return false;
+ }
+
+ boolean isDirectDescendantOfObject = true;
+ boolean implicitCallSuper = callSuper == null;
+ if (callSuper == null) {
+ try {
+ callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {
+ throw new InternalError("Lombok bug - this cannot happen - can't find callSuper field in EqualsAndHashCode annotation.");
+ }
+ }
+
+ JCTree extending = ((JCClassDecl)typeNode.get()).extending;
+ if (extending != null) {
+ String p = extending.toString();
+ isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object");
+ }
+
+ if (isDirectDescendantOfObject && callSuper) {
+ errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless.");
+ return true;
+ }
+
+ if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) {
+ errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.");
+ }
+
+ List<JavacNode> nodesForEquality = List.nil();
+ if (includes != null) {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ if (includes.contains(fieldDecl.name.toString())) nodesForEquality = nodesForEquality.append(child);
+ }
+ } else {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ //Skip static fields.
+ if ((fieldDecl.mods.flags & Flags.STATIC) != 0) continue;
+ //Skip transient fields.
+ if ((fieldDecl.mods.flags & Flags.TRANSIENT) != 0) continue;
+ //Skip excluded fields.
+ if (excludes != null && excludes.contains(fieldDecl.name.toString())) continue;
+ //Skip fields that start with $
+ if (fieldDecl.name.toString().startsWith("$")) continue;
+ nodesForEquality = nodesForEquality.append(child);
+ }
+ }
+
+ switch (methodExists("hashCode", typeNode)) {
+ case NOT_EXISTS:
+ JCMethodDecl method = createHashCode(typeNode, nodesForEquality, callSuper);
+ injectMethod(typeNode, method);
+ break;
+ case EXISTS_BY_LOMBOK:
+ break;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating hashCode(): A method with that name already exists");
+ }
+ break;
+ }
+
+ switch (methodExists("equals", typeNode)) {
+ case NOT_EXISTS:
+ JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper);
+ injectMethod(typeNode, method);
+ break;
+ case EXISTS_BY_LOMBOK:
+ break;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating equals(Object other): A method with that name already exists");
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ private JCMethodDecl createHashCode(JavacNode typeNode, List<JavacNode> fields, boolean callSuper) {
+ TreeMaker maker = typeNode.getTreeMaker();
+
+ JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.<JCExpression>nil());
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation));
+ JCExpression returnType = maker.TypeIdent(TypeTags.INT);
+ List<JCStatement> statements = List.nil();
+
+ Name primeName = typeNode.toName("PRIME");
+ Name resultName = typeNode.toName("result");
+ /* final int PRIME = 31; */ {
+ if (!fields.isEmpty() || callSuper) {
+ statements = statements.append(maker.VarDef(maker.Modifiers(Flags.FINAL),
+ primeName, maker.TypeIdent(TypeTags.INT), maker.Literal(31)));
+ }
+ }
+
+ /* int result = 1; */ {
+ statements = statements.append(maker.VarDef(maker.Modifiers(0), resultName, maker.TypeIdent(TypeTags.INT), maker.Literal(1)));
+ }
+
+ List<JCExpression> intoResult = List.nil();
+
+ if (callSuper) {
+ JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(),
+ maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")),
+ List.<JCExpression>nil());
+ intoResult = intoResult.append(callToSuper);
+ }
+
+ int tempCounter = 0;
+ for (JavacNode fieldNode : fields) {
+ JCVariableDecl field = (JCVariableDecl) fieldNode.get();
+ JCExpression fType = field.vartype;
+ JCExpression thisDotField = maker.Select(maker.Ident(typeNode.toName("this")), field.name);
+ JCExpression thisDotFieldClone = maker.Select(maker.Ident(typeNode.toName("this")), field.name);
+ if (fType instanceof JCPrimitiveTypeTree) {
+ switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) {
+ case BOOLEAN:
+ /* this.fieldName ? 1231 : 1237 */
+ intoResult = intoResult.append(maker.Conditional(thisDotField, maker.Literal(1231), maker.Literal(1237)));
+ break;
+ case LONG:
+ intoResult = intoResult.append(longToIntForHashCode(maker, thisDotField, thisDotFieldClone));
+ break;
+ case FLOAT:
+ /* Float.floatToIntBits(this.fieldName) */
+ intoResult = intoResult.append(maker.Apply(
+ List.<JCExpression>nil(),
+ chainDots(maker, typeNode, "java", "lang", "Float", "floatToIntBits"),
+ List.of(thisDotField)));
+ break;
+ case DOUBLE:
+ /* longToIntForHashCode(Double.doubleToLongBits(this.fieldName)) */
+ Name tempVar = typeNode.toName("temp" + (++tempCounter));
+ JCExpression init = maker.Apply(
+ List.<JCExpression>nil(),
+ chainDots(maker, typeNode, "java", "lang", "Double", "doubleToLongBits"),
+ List.of(thisDotField));
+ statements = statements.append(
+ maker.VarDef(maker.Modifiers(Flags.FINAL), tempVar, maker.TypeIdent(TypeTags.LONG), init));
+ intoResult = intoResult.append(longToIntForHashCode(maker, maker.Ident(tempVar), maker.Ident(tempVar)));
+ break;
+ default:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case CHAR:
+ /* just the field */
+ intoResult = intoResult.append(thisDotField);
+ break;
+ }
+ } else if (fType instanceof JCArrayTypeTree) {
+ /* java.util.Arrays.deepHashCode(this.fieldName) //use just hashCode() for primitive arrays. */
+ boolean multiDim = ((JCArrayTypeTree)fType).elemtype instanceof JCArrayTypeTree;
+ boolean primitiveArray = ((JCArrayTypeTree)fType).elemtype instanceof JCPrimitiveTypeTree;
+ boolean useDeepHC = multiDim || !primitiveArray;
+
+ JCExpression hcMethod = chainDots(maker, typeNode, "java", "util", "Arrays", useDeepHC ? "deepHashCode" : "hashCode");
+ intoResult = intoResult.append(
+ maker.Apply(List.<JCExpression>nil(), hcMethod, List.of(thisDotField)));
+ } else /* objects */ {
+ /* this.fieldName == null ? 0 : this.fieldName.hashCode() */
+ JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(thisDotField, typeNode.toName("hashCode")),
+ List.<JCExpression>nil());
+ JCExpression thisEqualsNull = maker.Binary(JCTree.EQ, thisDotField, maker.Literal(TypeTags.BOT, null));
+ intoResult = intoResult.append(
+ maker.Conditional(thisEqualsNull, maker.Literal(0), hcCall));
+ }
+ }
+
+ /* fold each intoResult entry into:
+ result = result * PRIME + (item); */
+ for (JCExpression expr : intoResult) {
+ JCExpression mult = maker.Binary(JCTree.MUL, maker.Ident(resultName), maker.Ident(primeName));
+ JCExpression add = maker.Binary(JCTree.PLUS, mult, expr);
+ statements = statements.append(maker.Exec(maker.Assign(maker.Ident(resultName), add)));
+ }
+
+ /* return result; */ {
+ statements = statements.append(maker.Return(maker.Ident(resultName)));
+ }
+
+ JCBlock body = maker.Block(0, statements);
+ return maker.MethodDef(mods, typeNode.toName("hashCode"), returnType,
+ List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null);
+ }
+
+ /** The 2 references must be clones of each other. */
+ private JCExpression longToIntForHashCode(TreeMaker maker, JCExpression ref1, JCExpression ref2) {
+ /* (int)(ref >>> 32 ^ ref) */
+ JCExpression shift = maker.Binary(JCTree.USR, ref1, maker.Literal(32));
+ JCExpression xorBits = maker.Binary(JCTree.BITXOR, shift, ref2);
+ return maker.TypeCast(maker.TypeIdent(TypeTags.INT), xorBits);
+ }
+
+ private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper) {
+ TreeMaker maker = typeNode.getTreeMaker();
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ Name oName = typeNode.toName("o");
+ Name otherName = typeNode.toName("other");
+ Name thisName = typeNode.toName("this");
+
+ JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.<JCExpression>nil());
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation));
+ JCExpression objectType = maker.Type(typeNode.getSymbolTable().objectType);
+ JCExpression returnType = maker.TypeIdent(TypeTags.BOOLEAN);
+
+ List<JCStatement> statements = List.nil();
+ List<JCVariableDecl> params = List.of(maker.VarDef(maker.Modifiers(Flags.FINAL), oName, objectType, null));
+
+ /* if (o == this) return true; */ {
+ statements = statements.append(maker.If(maker.Binary(JCTree.EQ, maker.Ident(oName),
+ maker.Ident(thisName)), returnBool(maker, true), null));
+ }
+
+ /* if (o == null) return false; */ {
+ statements = statements.append(maker.If(maker.Binary(JCTree.EQ, maker.Ident(oName),
+ maker.Literal(TypeTags.BOT, null)), returnBool(maker, false), null));
+ }
+
+ /* if (o.getClass() != this.getClass()) return false; */ {
+ Name getClass = typeNode.toName("getClass");
+ List<JCExpression> exprNil = List.nil();
+ JCExpression oGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(oName), getClass), exprNil);
+ JCExpression thisGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(thisName), getClass), exprNil);
+ statements = statements.append(
+ maker.If(maker.Binary(JCTree.NE, oGetClass, thisGetClass), returnBool(maker, false), null));
+ }
+
+ /* if (!super.equals(o)) return false; */
+ if (callSuper) {
+ JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(),
+ maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")),
+ List.<JCExpression>of(maker.Ident(oName)));
+ JCUnary superNotEqual = maker.Unary(JCTree.NOT, callToSuper);
+ statements = statements.append(maker.If(superNotEqual, returnBool(maker, false), null));
+ }
+
+ /* MyType<?> other = (MyType<?>) o; */ {
+ final JCExpression selfType1, selfType2;
+ List<JCExpression> wildcards1 = List.nil();
+ List<JCExpression> wildcards2 = List.nil();
+ for (int i = 0 ; i < type.typarams.length() ; i++) {
+ wildcards1 = wildcards1.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null));
+ wildcards2 = wildcards2.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null));
+ }
+
+ if (type.typarams.isEmpty()) {
+ selfType1 = maker.Ident(type.name);
+ selfType2 = maker.Ident(type.name);
+ } else {
+ selfType1 = maker.TypeApply(maker.Ident(type.name), wildcards1);
+ selfType2 = maker.TypeApply(maker.Ident(type.name), wildcards2);
+ }
+
+ statements = statements.append(
+ maker.VarDef(maker.Modifiers(Flags.FINAL), otherName, selfType1, maker.TypeCast(selfType2, maker.Ident(oName))));
+ }
+
+ for (JavacNode fieldNode : fields) {
+ JCVariableDecl field = (JCVariableDecl) fieldNode.get();
+ JCExpression fType = field.vartype;
+ JCExpression thisDotField = maker.Select(maker.Ident(thisName), field.name);
+ JCExpression otherDotField = maker.Select(maker.Ident(otherName), field.name);
+ if (fType instanceof JCPrimitiveTypeTree) {
+ switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) {
+ case FLOAT:
+ /* if (Float.compare(this.fieldName, other.fieldName) != 0) return false; */
+ statements = statements.append(generateCompareFloatOrDouble(thisDotField, otherDotField, maker, typeNode, false));
+ break;
+ case DOUBLE:
+ /* if (Double(this.fieldName, other.fieldName) != 0) return false; */
+ statements = statements.append(generateCompareFloatOrDouble(thisDotField, otherDotField, maker, typeNode, true));
+ break;
+ default:
+ /* if (this.fieldName != other.fieldName) return false; */
+ statements = statements.append(
+ maker.If(maker.Binary(JCTree.NE, thisDotField, otherDotField), returnBool(maker, false), null));
+ break;
+ }
+ } else if (fType instanceof JCArrayTypeTree) {
+ /* if (!java.util.Arrays.deepEquals(this.fieldName, other.fieldName)) return false; //use equals for primitive arrays. */
+ boolean multiDim = ((JCArrayTypeTree)fType).elemtype instanceof JCArrayTypeTree;
+ boolean primitiveArray = ((JCArrayTypeTree)fType).elemtype instanceof JCPrimitiveTypeTree;
+ boolean useDeepEquals = multiDim || !primitiveArray;
+
+ JCExpression eqMethod = chainDots(maker, typeNode, "java", "util", "Arrays", useDeepEquals ? "deepEquals" : "equals");
+ List<JCExpression> args = List.of(thisDotField, otherDotField);
+ statements = statements.append(maker.If(maker.Unary(JCTree.NOT,
+ maker.Apply(List.<JCExpression>nil(), eqMethod, args)), returnBool(maker, false), null));
+ } else /* objects */ {
+ /* if (this.fieldName == null ? other.fieldName != null : !this.fieldName.equals(other.fieldName)) return false; */
+ JCExpression thisEqualsNull = maker.Binary(JCTree.EQ, thisDotField, maker.Literal(TypeTags.BOT, null));
+ JCExpression otherNotEqualsNull = maker.Binary(JCTree.NE, otherDotField, maker.Literal(TypeTags.BOT, null));
+ JCExpression thisEqualsThat = maker.Apply(
+ List.<JCExpression>nil(), maker.Select(thisDotField, typeNode.toName("equals")), List.of(otherDotField));
+ JCExpression fieldsAreNotEqual = maker.Conditional(thisEqualsNull, otherNotEqualsNull, maker.Unary(JCTree.NOT, thisEqualsThat));
+ statements = statements.append(maker.If(fieldsAreNotEqual, returnBool(maker, false), null));
+ }
+ }
+
+ /* return true; */ {
+ statements = statements.append(returnBool(maker, true));
+ }
+
+ JCBlock body = maker.Block(0, statements);
+ return maker.MethodDef(mods, typeNode.toName("equals"), returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null);
+ }
+
+ private JCStatement generateCompareFloatOrDouble(JCExpression thisDotField, JCExpression otherDotField,
+ TreeMaker maker, JavacNode node, boolean isDouble) {
+ /* if (Float.compare(fieldName, other.fieldName) != 0) return false; */
+ JCExpression clazz = chainDots(maker, node, "java", "lang", isDouble ? "Double" : "Float");
+ List<JCExpression> args = List.of(thisDotField, otherDotField);
+ JCBinary compareCallEquals0 = maker.Binary(JCTree.NE, maker.Apply(
+ List.<JCExpression>nil(), maker.Select(clazz, node.toName("compare")), args), maker.Literal(0));
+ return maker.If(compareCallEquals0, returnBool(maker, false), null);
+ }
+
+ private JCStatement returnBool(TreeMaker maker, boolean bool) {
+ return maker.Return(maker.Literal(TypeTags.BOOLEAN, bool ? 1 : 0));
+ }
+
+}
diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java
new file mode 100644
index 00000000..e60e426d
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleGetter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.javac.Javac;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+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.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+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;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+
+/**
+ * Handles the {@code lombok.Getter} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleGetter implements JavacAnnotationHandler<Getter> {
+ /**
+ * Generates a getter on the stated field.
+ *
+ * Used by {@link HandleData}.
+ *
+ * The difference between this call and the handle method is as follows:
+ *
+ * If there is a {@code lombok.Getter} annotation on the field, it is used and the
+ * same rules apply (e.g. warning if the method already exists, stated access level applies).
+ * If not, the getter is still generated if it isn't already there, though there will not
+ * be a warning if its already there. The default access level is used.
+ *
+ * @param fieldNode The node representing the field you want a getter for.
+ * @param pos The node responsible for generating the getter (the {@code @Data} or {@code @Getter} annotation).
+ */
+ public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos) {
+ for (JavacNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Javac.annotationTypeMatches(Getter.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ createGetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, false);
+ }
+
+ @Override public boolean handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ JavacNode fieldNode = annotationNode.up();
+ AccessLevel level = annotation.getInstance().value();
+ if (level == AccessLevel.NONE) return true;
+
+ return createGetterForField(level, fieldNode, annotationNode, true);
+ }
+
+ private boolean createGetterForField(AccessLevel level,
+ JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists) {
+ if (fieldNode.getKind() != Kind.FIELD) {
+ errorNode.addError("@Getter is only supported on a field.");
+ return true;
+ }
+
+ JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get();
+ String methodName = toGetterName(fieldDecl);
+
+ for (String altName : toAllGetterNames(fieldDecl)) {
+ switch (methodExists(altName, fieldNode)) {
+ case EXISTS_BY_LOMBOK:
+ return true;
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ String altNameExpl = "";
+ if (!altName.equals(methodName)) altNameExpl = String.format(" (%s)", altName);
+ errorNode.addWarning(
+ String.format("Not generating %s(): A method with that name already exists%s", methodName, altNameExpl));
+ }
+ return true;
+ default:
+ case NOT_EXISTS:
+ //continue scanning the other alt names.
+ }
+ }
+
+ long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC);
+
+ injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker()));
+
+ return true;
+ }
+
+ private JCMethodDecl createGetter(long access, JavacNode field, TreeMaker treeMaker) {
+ JCVariableDecl fieldNode = (JCVariableDecl) field.get();
+ JCStatement returnStatement = treeMaker.Return(treeMaker.Ident(fieldNode.getName()));
+
+ JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement));
+ Name methodName = field.toName(toGetterName(fieldNode));
+ JCExpression methodType = fieldNode.type != null ? treeMaker.Type(fieldNode.type) : fieldNode.vartype;
+
+ List<JCTypeParameter> methodGenericParams = List.nil();
+ List<JCVariableDecl> parameters = List.nil();
+ List<JCExpression> throwsClauses = List.nil();
+ JCExpression annotationMethodDefaultValue = null;
+
+ List<JCAnnotation> nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
+ List<JCAnnotation> nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);
+ return treeMaker.MethodDef(treeMaker.Modifiers(access, nonNulls.appendList(nullables)), methodName, methodType,
+ methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue);
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandlePrintAST.java b/src/core/lombok/javac/handlers/HandlePrintAST.java
new file mode 100644
index 00000000..4c25694b
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandlePrintAST.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+
+import lombok.Lombok;
+import lombok.core.AnnotationValues;
+import lombok.core.PrintAST;
+import lombok.javac.JavacASTVisitor;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+/**
+ * Handles the {@code lombok.core.PrintAST} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandlePrintAST implements JavacAnnotationHandler<PrintAST> {
+ @Override public boolean handle(AnnotationValues<PrintAST> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ PrintStream stream = System.out;
+ String fileName = annotation.getInstance().outfile();
+ if (fileName.length() > 0) try {
+ stream = new PrintStream(new File(fileName));
+ } catch (FileNotFoundException e) {
+ Lombok.sneakyThrow(e);
+ }
+
+ annotationNode.up().traverse(new JavacASTVisitor.Printer(annotation.getInstance().printContent(), stream));
+
+ return true;
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java
new file mode 100644
index 00000000..84032e9c
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleSetter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+import lombok.AccessLevel;
+import lombok.Setter;
+import lombok.core.AnnotationValues;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.javac.Javac;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+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;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+
+/**
+ * Handles the {@code lombok.Setter} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleSetter implements JavacAnnotationHandler<Setter> {
+ /**
+ * Generates a setter on the stated field.
+ *
+ * Used by {@link HandleData}.
+ *
+ * The difference between this call and the handle method is as follows:
+ *
+ * If there is a {@code lombok.Setter} annotation on the field, it is used and the
+ * same rules apply (e.g. warning if the method already exists, stated access level applies).
+ * If not, the setter is still generated if it isn't already there, though there will not
+ * be a warning if its already there. The default access level is used.
+ *
+ * @param fieldNode The node representing the field you want a setter for.
+ * @param pos The node responsible for generating the setter (the {@code @Data} or {@code @Setter} annotation).
+ */
+ public void generateSetterForField(JavacNode fieldNode, DiagnosticPosition pos) {
+ for (JavacNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Javac.annotationTypeMatches(Setter.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, false);
+ }
+
+ @Override public boolean handle(AnnotationValues<Setter> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ JavacNode fieldNode = annotationNode.up();
+ AccessLevel level = annotation.getInstance().value();
+ if (level == AccessLevel.NONE) return true;
+
+ return createSetterForField(level, fieldNode, annotationNode, true);
+ }
+
+ private boolean createSetterForField(AccessLevel level,
+ JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists) {
+ if (fieldNode.getKind() != Kind.FIELD) {
+ fieldNode.addError("@Setter is only supported on a field.");
+ return true;
+ }
+
+ JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get();
+ String methodName = toSetterName(fieldDecl);
+
+ switch (methodExists(methodName, fieldNode)) {
+ case EXISTS_BY_LOMBOK:
+ return true;
+ case EXISTS_BY_USER:
+ if (whineIfExists) errorNode.addWarning(
+ String.format("Not generating %s(%s %s): A method with that name already exists",
+ methodName, fieldDecl.vartype, fieldDecl.name));
+ return true;
+ default:
+ case NOT_EXISTS:
+ //continue with creating the setter
+ }
+
+ long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC);
+
+ injectMethod(fieldNode.up(), createSetter(access, fieldNode, fieldNode.getTreeMaker()));
+
+ return true;
+ }
+
+ private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker) {
+ JCVariableDecl fieldDecl = (JCVariableDecl) field.get();
+
+ JCFieldAccess thisX = treeMaker.Select(treeMaker.Ident(field.toName("this")), fieldDecl.name);
+ JCAssign assign = treeMaker.Assign(thisX, treeMaker.Ident(fieldDecl.name));
+
+ List<JCStatement> statements;
+ List<JCAnnotation> nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
+ List<JCAnnotation> nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);
+
+ if (nonNulls.isEmpty()) {
+ statements = List.<JCStatement>of(treeMaker.Exec(assign));
+ } else {
+ JCStatement nullCheck = generateNullCheck(treeMaker, field);
+ if (nullCheck != null) statements = List.<JCStatement>of(nullCheck, treeMaker.Exec(assign));
+ else statements = List.<JCStatement>of(treeMaker.Exec(assign));
+ }
+
+ JCBlock methodBody = treeMaker.Block(0, statements);
+ Name methodName = field.toName(toSetterName(fieldDecl));
+ JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), fieldDecl.name, fieldDecl.vartype, null);
+ JCExpression methodType = treeMaker.Type(field.getSymbolTable().voidType);
+
+ List<JCTypeParameter> methodGenericParams = List.nil();
+ List<JCVariableDecl> parameters = List.of(param);
+ List<JCExpression> throwsClauses = List.nil();
+ JCExpression annotationMethodDefaultValue = null;
+
+ return treeMaker.MethodDef(treeMaker.Modifiers(access, List.<JCAnnotation>nil()), methodName, methodType,
+ methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue);
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java
new file mode 100644
index 00000000..e7879dd1
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.chainDots;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import lombok.SneakyThrows;
+import lombok.core.AnnotationValues;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+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.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+
+/**
+ * Handles the {@code lombok.SneakyThrows} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleSneakyThrows implements JavacAnnotationHandler<SneakyThrows> {
+ @Override public boolean handle(AnnotationValues<SneakyThrows> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ Collection<String> exceptionNames = annotation.getRawExpressions("value");
+
+ List<JCExpression> memberValuePairs = ast.getArguments();
+ if (memberValuePairs == null || memberValuePairs.size() == 0) return false;
+
+ java.util.List<String> exceptions = new ArrayList<String>();
+ for (String exception : exceptionNames) {
+ if (exception.endsWith(".class")) exception = exception.substring(0, exception.length() - 6);
+ exceptions.add(exception);
+ }
+
+ JavacNode owner = annotationNode.up();
+ switch (owner.getKind()) {
+ case METHOD:
+ return handleMethod(annotationNode, (JCMethodDecl)owner.get(), exceptions);
+ default:
+ annotationNode.addError("@SneakyThrows is legal only on methods and constructors.");
+ return true;
+ }
+ }
+
+ private boolean handleMethod(JavacNode annotation, JCMethodDecl method, Collection<String> exceptions) {
+ JavacNode methodNode = annotation.up();
+
+ if ( (method.mods.flags & Flags.ABSTRACT) != 0) {
+ annotation.addError("@SneakyThrows can only be used on concrete methods.");
+ return true;
+ }
+
+ if (method.body == null) return false;
+
+ List<JCStatement> contents = method.body.stats;
+
+ for (String exception : exceptions) {
+ contents = List.of(buildTryCatchBlock(methodNode, contents, exception));
+ }
+
+ method.body.stats = contents;
+ methodNode.rebuild();
+
+ return true;
+ }
+
+ private JCStatement buildTryCatchBlock(JavacNode node, List<JCStatement> contents, String exception) {
+ TreeMaker maker = node.getTreeMaker();
+
+ JCBlock tryBlock = maker.Block(0, contents);
+
+ JCExpression varType = chainDots(maker, node, exception.split("\\."));
+
+ JCVariableDecl catchParam = maker.VarDef(maker.Modifiers(0), node.toName("$ex"), varType, null);
+ JCExpression lombokLombokSneakyThrowNameRef = chainDots(maker, node, "lombok", "Lombok", "sneakyThrow");
+ JCBlock catchBody = maker.Block(0, List.<JCStatement>of(maker.Throw(maker.Apply(
+ List.<JCExpression>nil(), lombokLombokSneakyThrowNameRef,
+ List.<JCExpression>of(maker.Ident(node.toName("$ex")))))));
+
+ return maker.Try(tryBlock, List.of(maker.Catch(catchParam, catchBody)), null);
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleSynchronized.java b/src/core/lombok/javac/handlers/HandleSynchronized.java
new file mode 100644
index 00000000..c86d99c6
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleSynchronized.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.TypeTags;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+
+import lombok.Synchronized;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+/**
+ * Handles the {@code lombok.Synchronized} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleSynchronized implements JavacAnnotationHandler<Synchronized> {
+ private static final String INSTANCE_LOCK_NAME = "$lock";
+ private static final String STATIC_LOCK_NAME = "$LOCK";
+
+ @Override public boolean handle(AnnotationValues<Synchronized> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ JavacNode methodNode = annotationNode.up();
+
+ if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof JCMethodDecl)) {
+ annotationNode.addError("@Synchronized is legal only on methods.");
+ return true;
+ }
+
+ JCMethodDecl method = (JCMethodDecl)methodNode.get();
+
+ if ((method.mods.flags & Flags.ABSTRACT) != 0) {
+ annotationNode.addError("@Synchronized is legal only on concrete methods.");
+ return true;
+ }
+ boolean isStatic = (method.mods.flags & Flags.STATIC) != 0;
+ String lockName = annotation.getInstance().value();
+ boolean autoMake = false;
+ if (lockName.length() == 0) {
+ autoMake = true;
+ lockName = isStatic ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME;
+ }
+
+ TreeMaker maker = methodNode.getTreeMaker();
+
+ if (fieldExists(lockName, methodNode) == MemberExistsResult.NOT_EXISTS) {
+ if (!autoMake) {
+ annotationNode.addError("The field " + lockName + " does not exist.");
+ return true;
+ }
+ JCExpression objectType = chainDots(maker, methodNode, "java", "lang", "Object");
+ //We use 'new Object[0];' because quite unlike 'new Object();', empty arrays *ARE* serializable!
+ JCNewArray newObjectArray = maker.NewArray(chainDots(maker, methodNode, "java", "lang", "Object"),
+ List.<JCExpression>of(maker.Literal(TypeTags.INT, 0)), null);
+ JCVariableDecl fieldDecl = maker.VarDef(
+ maker.Modifiers(Flags.FINAL | (isStatic ? Flags.STATIC : 0)),
+ methodNode.toName(lockName), objectType, newObjectArray);
+ injectField(methodNode.up(), fieldDecl);
+ }
+
+ if (method.body == null) return false;
+
+ JCExpression lockNode = maker.Ident(methodNode.toName(lockName));
+
+ method.body = maker.Block(0, List.<JCStatement>of(maker.Synchronized(lockNode, method.body)));
+
+ methodNode.rebuild();
+
+ return true;
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java
new file mode 100644
index 00000000..f7251ab8
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleToString.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import lombok.ToString;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.javac.Javac;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+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.JCArrayTypeTree;
+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.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+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;
+
+/**
+ * Handles the {@code ToString} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+public class HandleToString implements JavacAnnotationHandler<ToString> {
+ private void checkForBogusFieldNames(JavacNode type, AnnotationValues<ToString> annotation) {
+ if (annotation.isExplicit("exclude")) {
+ for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, false)) {
+ annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
+ }
+ }
+ if (annotation.isExplicit("of")) {
+ for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().of()), type, false, false)) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
+ }
+
+ @Override public boolean handle(AnnotationValues<ToString> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ ToString ann = annotation.getInstance();
+ List<String> excludes = List.from(ann.exclude());
+ List<String> includes = List.from(ann.of());
+ JavacNode typeNode = annotationNode.up();
+
+ checkForBogusFieldNames(typeNode, annotation);
+
+ Boolean callSuper = ann.callSuper();
+
+ if (!annotation.isExplicit("callSuper")) callSuper = null;
+ if (!annotation.isExplicit("exclude")) excludes = null;
+ if (!annotation.isExplicit("of")) includes = null;
+
+ if (excludes != null && includes != null) {
+ excludes = null;
+ annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored.");
+ }
+
+ return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true);
+ }
+
+ public void generateToStringForType(JavacNode typeNode, JavacNode errorNode) {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ if (Javac.annotationTypeMatches(ToString.class, child)) {
+ //The annotation will make it happen, so we can skip it.
+ return;
+ }
+ }
+ }
+
+ boolean includeFieldNames = true;
+ try {
+ includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {}
+ generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
+ }
+
+ private boolean generateToString(JavacNode typeNode, JavacNode errorNode, List<String> excludes, List<String> includes,
+ boolean includeFieldNames, Boolean callSuper, boolean whineIfExists) {
+ boolean notAClass = true;
+ if (typeNode.get() instanceof JCClassDecl) {
+ long flags = ((JCClassDecl)typeNode.get()).mods.flags;
+ notAClass = (flags & (Flags.INTERFACE | Flags.ANNOTATION | Flags.ENUM)) != 0;
+ }
+
+ if (callSuper == null) {
+ try {
+ callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch (Exception ignore) {}
+ }
+
+ if (notAClass) {
+ errorNode.addError("@ToString is only supported on a class.");
+ return false;
+ }
+
+ List<JavacNode> nodesForToString = List.nil();
+ if (includes != null) {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ if (includes.contains(fieldDecl.name.toString())) nodesForToString = nodesForToString.append(child);
+ }
+ } else {
+ for (JavacNode child : typeNode.down()) {
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ //Skip static fields.
+ if ((fieldDecl.mods.flags & Flags.STATIC) != 0) continue;
+ //Skip excluded fields.
+ if (excludes != null && excludes.contains(fieldDecl.name.toString())) continue;
+ //Skip fields that start with $.
+ if (fieldDecl.name.toString().startsWith("$")) continue;
+ nodesForToString = nodesForToString.append(child);
+ }
+ }
+
+ switch (methodExists("toString", typeNode)) {
+ case NOT_EXISTS:
+ JCMethodDecl method = createToString(typeNode, nodesForToString, includeFieldNames, callSuper);
+ injectMethod(typeNode, method);
+ return true;
+ case EXISTS_BY_LOMBOK:
+ return true;
+ default:
+ case EXISTS_BY_USER:
+ if (whineIfExists) {
+ errorNode.addWarning("Not generating toString(): A method with that name already exists");
+ }
+ return true;
+ }
+
+ }
+
+ private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper) {
+ TreeMaker maker = typeNode.getTreeMaker();
+
+ JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.<JCExpression>nil());
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation));
+ JCExpression returnType = chainDots(maker, typeNode, "java", "lang", "String");
+
+ boolean first = true;
+
+ String typeName = ((JCClassDecl) typeNode.get()).name.toString();
+ String infix = ", ";
+ String suffix = ")";
+ String prefix;
+ if (callSuper) {
+ prefix = typeName + "(super=";
+ } else if (fields.isEmpty()) {
+ prefix = typeName + "()";
+ } else if (includeFieldNames) {
+ prefix = typeName + "(" + ((JCVariableDecl)fields.iterator().next().get()).name.toString() + "=";
+ } else {
+ prefix = typeName + "(";
+ }
+
+ JCExpression current = maker.Literal(prefix);
+
+ if (callSuper) {
+ JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(),
+ maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("toString")),
+ List.<JCExpression>nil());
+ current = maker.Binary(JCTree.PLUS, current, callToSuper);
+ first = false;
+ }
+
+ for (JavacNode fieldNode : fields) {
+ JCVariableDecl field = (JCVariableDecl) fieldNode.get();
+ JCExpression expr;
+
+ if (field.vartype instanceof JCArrayTypeTree) {
+ boolean multiDim = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCArrayTypeTree;
+ boolean primitiveArray = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCPrimitiveTypeTree;
+ boolean useDeepTS = multiDim || !primitiveArray;
+
+ JCExpression hcMethod = chainDots(maker, typeNode, "java", "util", "Arrays", useDeepTS ? "deepToString" : "toString");
+ expr = maker.Apply(List.<JCExpression>nil(), hcMethod, List.<JCExpression>of(maker.Ident(field.name)));
+ } else expr = maker.Ident(field.name);
+
+ if (first) {
+ current = maker.Binary(JCTree.PLUS, current, expr);
+ first = false;
+ continue;
+ }
+
+ if (includeFieldNames) {
+ current = maker.Binary(JCTree.PLUS, current, maker.Literal(infix + fieldNode.getName() + "="));
+ } else {
+ current = maker.Binary(JCTree.PLUS, current, maker.Literal(infix));
+ }
+
+ current = maker.Binary(JCTree.PLUS, current, expr);
+ }
+
+ if (!first) current = maker.Binary(JCTree.PLUS, current, maker.Literal(suffix));
+
+ JCStatement returnStatement = maker.Return(current);
+
+ JCBlock body = maker.Block(0, List.of(returnStatement));
+
+ return maker.MethodDef(mods, typeNode.toName("toString"), returnType,
+ List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null);
+ }
+
+}
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
new file mode 100644
index 00000000..34d8b849
--- /dev/null
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import java.util.regex.Pattern;
+
+import lombok.AccessLevel;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacNode;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.TypeTags;
+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.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+
+/**
+ * Container for static utility methods useful to handlers written for javac.
+ */
+public class JavacHandlerUtil {
+ private JavacHandlerUtil() {
+ //Prevent instantiation
+ }
+
+ /**
+ * Checks if the given expression (that really ought to refer to a type expression) represents a primitive type.
+ */
+ public static boolean isPrimitive(JCExpression ref) {
+ String typeName = ref.toString();
+ return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(typeName).matches();
+ }
+
+ /**
+ * Translates the given field into all possible getter names.
+ * Convenient wrapper around {@link TransformationsUtil#toAllGetterNames(CharSequence, boolean)}.
+ */
+ public static java.util.List<String> toAllGetterNames(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ boolean isBoolean = field.vartype.toString().equals("boolean");
+
+ return TransformationsUtil.toAllGetterNames(fieldName, isBoolean);
+ }
+
+ /**
+ * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo).
+ *
+ * Convenient wrapper around {@link TransformationsUtil#toGetterName(CharSequence, boolean)}.
+ */
+ public static String toGetterName(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ boolean isBoolean = field.vartype.toString().equals("boolean");
+
+ return TransformationsUtil.toGetterName(fieldName, isBoolean);
+ }
+
+ /**
+ * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo).
+ *
+ * Convenient wrapper around {@link TransformationsUtil#toSetterName(CharSequence)}.
+ */
+ public static String toSetterName(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ return TransformationsUtil.toSetterName(fieldName);
+ }
+
+ /** Serves as return value for the methods that check for the existence of fields and methods. */
+ public enum MemberExistsResult {
+ NOT_EXISTS, EXISTS_BY_USER, EXISTS_BY_LOMBOK;
+ }
+
+ /**
+ * Checks if there is a field with the provided name.
+ *
+ * @param fieldName the field name to check for.
+ * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult fieldExists(String fieldName, JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCVariableDecl) {
+ if (((JCVariableDecl)def).name.contentEquals(fieldName)) {
+ JavacNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Checks if there is a method with the provided name. In case of multiple methods (overloading), only
+ * the first method decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
+ *
+ * @param methodName the method name to check for.
+ * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult methodExists(String methodName, JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCMethodDecl) {
+ if (((JCMethodDecl)def).name.contentEquals(methodName)) {
+ JavacNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only
+ * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
+ *
+ * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult constructorExists(JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCMethodDecl) {
+ if (((JCMethodDecl)def).name.contentEquals("<init>")) {
+ if ((((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0) continue;
+ JavacNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Turns an {@code AccessLevel} instance into the flag bit used by javac.
+ */
+ public static int toJavacModifier(AccessLevel accessLevel) {
+ switch (accessLevel) {
+ case MODULE:
+ case PACKAGE:
+ return 0;
+ default:
+ case PUBLIC:
+ return Flags.PUBLIC;
+ case PRIVATE:
+ return Flags.PRIVATE;
+ case PROTECTED:
+ return Flags.PROTECTED;
+ }
+ }
+
+ /**
+ * Adds the given new field declaration to the provided type AST Node.
+ *
+ * Also takes care of updating the JavacAST.
+ */
+ public static void injectField(JavacNode typeNode, JCVariableDecl field) {
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ type.defs = type.defs.append(field);
+
+ typeNode.add(field, Kind.FIELD).recursiveSetHandled();
+ }
+
+ /**
+ * Adds the given new method declaration to the provided type AST Node.
+ * Can also inject constructors.
+ *
+ * Also takes care of updating the JavacAST.
+ */
+ public static void injectMethod(JavacNode typeNode, JCMethodDecl method) {
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ if (method.getName().contentEquals("<init>")) {
+ //Scan for default constructor, and remove it.
+ int idx = 0;
+ for (JCTree def : type.defs) {
+ if (def instanceof JCMethodDecl) {
+ if ((((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0) {
+ JavacNode tossMe = typeNode.getNodeFor(def);
+ if (tossMe != null) tossMe.up().removeChild(tossMe);
+ type.defs = addAllButOne(type.defs, idx);
+ break;
+ }
+ }
+ idx++;
+ }
+ }
+
+ type.defs = type.defs.append(method);
+
+ typeNode.add(method, Kind.METHOD).recursiveSetHandled();
+ }
+
+ private static List<JCTree> addAllButOne(List<JCTree> defs, int idx) {
+ List<JCTree> out = List.nil();
+ int i = 0;
+ for (JCTree def : defs) {
+ if (i++ != idx) out = out.append(def);
+ }
+ return out;
+ }
+
+ /**
+ * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
+ * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
+ * a {@code Ident} node. This method generates such an expression.
+ *
+ * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]).
+ *
+ * @see com.sun.tools.javac.tree.JCTree.JCIdent
+ * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess
+ */
+ public static JCExpression chainDots(TreeMaker maker, JavacNode node, String... elems) {
+ assert elems != null;
+ assert elems.length > 0;
+
+ JCExpression e = maker.Ident(node.toName(elems[0]));
+ for (int i = 1 ; i < elems.length ; i++) {
+ e = maker.Select(e, node.toName(elems[i]));
+ }
+
+ return e;
+ }
+
+ /**
+ * Searches the given field node for annotations and returns each one that matches the provided regular expression pattern.
+ *
+ * Only the simple name is checked - the package and any containing class are ignored.
+ */
+ public static List<JCAnnotation> findAnnotations(JavacNode fieldNode, Pattern namePattern) {
+ List<JCAnnotation> result = List.nil();
+ for (JavacNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ JCAnnotation annotation = (JCAnnotation) child.get();
+ String name = annotation.annotationType.toString();
+ int idx = name.lastIndexOf(".");
+ String suspect = idx == -1 ? name : name.substring(idx + 1);
+ if (namePattern.matcher(suspect).matches()) {
+ result = result.append(annotation);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Generates a new statement that checks if the given variable is null, and if so, throws a {@code NullPointerException} with the
+ * variable name as message.
+ */
+ public static JCStatement generateNullCheck(TreeMaker treeMaker, JavacNode variable) {
+ JCVariableDecl varDecl = (JCVariableDecl) variable.get();
+ if (isPrimitive(varDecl.vartype)) return null;
+ Name fieldName = varDecl.name;
+ JCExpression npe = chainDots(treeMaker, variable, "java", "lang", "NullPointerException");
+ JCTree exception = treeMaker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(treeMaker.Literal(fieldName.toString())), null);
+ JCStatement throwStatement = treeMaker.Throw(exception);
+ return treeMaker.If(treeMaker.Binary(JCTree.EQ, treeMaker.Ident(fieldName), treeMaker.Literal(TypeTags.BOT, null)), throwStatement, null);
+ }
+
+ /**
+ * Given a list of field names and a node referring to a type, finds each name in the list that does not match a field within the type.
+ */
+ public static List<Integer> createListOfNonExistentFields(List<String> list, JavacNode type, boolean excludeStandard, boolean excludeTransient) {
+ boolean[] matched = new boolean[list.size()];
+
+ for (JavacNode child : type.down()) {
+ if (list.isEmpty()) break;
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl field = (JCVariableDecl)child.get();
+ if (excludeStandard) {
+ if ((field.mods.flags & Flags.STATIC) != 0) continue;
+ if (field.name.toString().startsWith("$")) continue;
+ }
+ if (excludeTransient && (field.mods.flags & Flags.TRANSIENT) != 0) continue;
+
+ int idx = list.indexOf(child.getName());
+ if (idx > -1) matched[idx] = true;
+ }
+
+ List<Integer> problematic = List.nil();
+ for (int i = 0 ; i < list.size() ; i++) {
+ if (!matched[i]) problematic = problematic.append(i);
+ }
+
+ return problematic;
+ }
+}
diff --git a/src/core/lombok/javac/handlers/package-info.java b/src/core/lombok/javac/handlers/package-info.java
new file mode 100644
index 00000000..b08d6af3
--- /dev/null
+++ b/src/core/lombok/javac/handlers/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Contains the classes that implement the transformations for all of lombok's various features on the javac v1.6 platform.
+ */
+package lombok.javac.handlers;
diff --git a/src/core/lombok/javac/package-info.java b/src/core/lombok/javac/package-info.java
new file mode 100644
index 00000000..0df2f050
--- /dev/null
+++ b/src/core/lombok/javac/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Includes the javac v1.6-specific implementations of the lombok AST and annotation introspection support.
+ */
+package lombok.javac;
diff --git a/src/core/lombok/package-info.java b/src/core/lombok/package-info.java
new file mode 100644
index 00000000..6d5af3d1
--- /dev/null
+++ b/src/core/lombok/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2009 Reinier Zwitserloot and Roel Spilker.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * This package contains all the annotations and support classes you need as a user of lombok.
+ * All other packages are only relevant to those who are extending lombok for their own uses.
+ */
+package lombok;