From 1a0e611a9c5e1ee518670647ce1a44beae559b44 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Wed, 25 Nov 2009 07:32:49 +0100 Subject: Refactored the source folders. --- src/core/lombok/AccessLevel.java | 31 + src/core/lombok/Cleanup.java | 82 +++ src/core/lombok/Data.java | 60 ++ src/core/lombok/EqualsAndHashCode.java | 80 +++ src/core/lombok/Getter.java | 58 ++ src/core/lombok/Lombok.java | 60 ++ src/core/lombok/NonNull.java | 43 ++ src/core/lombok/Setter.java | 55 ++ src/core/lombok/SneakyThrows.java | 73 +++ src/core/lombok/Synchronized.java | 46 ++ src/core/lombok/ToString.java | 81 +++ src/core/lombok/core/AST.java | 367 +++++++++++ src/core/lombok/core/AnnotationValues.java | 419 ++++++++++++ src/core/lombok/core/LombokNode.java | 297 +++++++++ src/core/lombok/core/PrintAST.java | 51 ++ src/core/lombok/core/SpiLoadUtil.java | 164 +++++ src/core/lombok/core/TransformationsUtil.java | 149 +++++ src/core/lombok/core/TypeLibrary.java | 79 +++ src/core/lombok/core/TypeResolver.java | 114 ++++ src/core/lombok/core/Version.java | 48 ++ src/core/lombok/core/package-info.java | 30 + src/core/lombok/eclipse/Eclipse.java | 479 ++++++++++++++ src/core/lombok/eclipse/EclipseAST.java | 366 +++++++++++ src/core/lombok/eclipse/EclipseASTAdapter.java | 101 +++ src/core/lombok/eclipse/EclipseASTVisitor.java | 295 +++++++++ .../lombok/eclipse/EclipseAnnotationHandler.java | 54 ++ src/core/lombok/eclipse/EclipseNode.java | 175 +++++ src/core/lombok/eclipse/HandlerLibrary.java | 200 ++++++ src/core/lombok/eclipse/TransformEclipseAST.java | 196 ++++++ .../eclipse/handlers/EclipseHandlerUtil.java | 385 +++++++++++ .../lombok/eclipse/handlers/HandleCleanup.java | 200 ++++++ src/core/lombok/eclipse/handlers/HandleData.java | 243 +++++++ .../eclipse/handlers/HandleEqualsAndHashCode.java | 718 +++++++++++++++++++++ src/core/lombok/eclipse/handlers/HandleGetter.java | 154 +++++ .../lombok/eclipse/handlers/HandlePrintAST.java | 57 ++ src/core/lombok/eclipse/handlers/HandleSetter.java | 172 +++++ .../eclipse/handlers/HandleSneakyThrows.java | 224 +++++++ .../eclipse/handlers/HandleSynchronized.java | 132 ++++ .../lombok/eclipse/handlers/HandleToString.java | 304 +++++++++ src/core/lombok/eclipse/handlers/package-info.java | 26 + src/core/lombok/eclipse/package-info.java | 26 + src/core/lombok/javac/HandlerLibrary.java | 219 +++++++ src/core/lombok/javac/Javac.java | 162 +++++ src/core/lombok/javac/JavacAST.java | 347 ++++++++++ src/core/lombok/javac/JavacASTAdapter.java | 98 +++ src/core/lombok/javac/JavacASTVisitor.java | 266 ++++++++ src/core/lombok/javac/JavacAnnotationHandler.java | 58 ++ src/core/lombok/javac/JavacNode.java | 212 ++++++ src/core/lombok/javac/apt/Processor.java | 175 +++++ src/core/lombok/javac/apt/package-info.java | 26 + src/core/lombok/javac/handlers/HandleCleanup.java | 147 +++++ src/core/lombok/javac/handlers/HandleData.java | 186 ++++++ .../javac/handlers/HandleEqualsAndHashCode.java | 447 +++++++++++++ src/core/lombok/javac/handlers/HandleGetter.java | 143 ++++ src/core/lombok/javac/handlers/HandlePrintAST.java | 57 ++ src/core/lombok/javac/handlers/HandleSetter.java | 153 +++++ .../lombok/javac/handlers/HandleSneakyThrows.java | 110 ++++ .../lombok/javac/handlers/HandleSynchronized.java | 102 +++ src/core/lombok/javac/handlers/HandleToString.java | 237 +++++++ .../lombok/javac/handlers/JavacHandlerUtil.java | 335 ++++++++++ src/core/lombok/javac/handlers/package-info.java | 26 + src/core/lombok/javac/package-info.java | 26 + src/core/lombok/package-info.java | 27 + 63 files changed, 10453 insertions(+) create mode 100644 src/core/lombok/AccessLevel.java create mode 100644 src/core/lombok/Cleanup.java create mode 100644 src/core/lombok/Data.java create mode 100644 src/core/lombok/EqualsAndHashCode.java create mode 100644 src/core/lombok/Getter.java create mode 100644 src/core/lombok/Lombok.java create mode 100644 src/core/lombok/NonNull.java create mode 100644 src/core/lombok/Setter.java create mode 100644 src/core/lombok/SneakyThrows.java create mode 100644 src/core/lombok/Synchronized.java create mode 100644 src/core/lombok/ToString.java create mode 100644 src/core/lombok/core/AST.java create mode 100644 src/core/lombok/core/AnnotationValues.java create mode 100644 src/core/lombok/core/LombokNode.java create mode 100644 src/core/lombok/core/PrintAST.java create mode 100644 src/core/lombok/core/SpiLoadUtil.java create mode 100644 src/core/lombok/core/TransformationsUtil.java create mode 100644 src/core/lombok/core/TypeLibrary.java create mode 100644 src/core/lombok/core/TypeResolver.java create mode 100644 src/core/lombok/core/Version.java create mode 100644 src/core/lombok/core/package-info.java create mode 100644 src/core/lombok/eclipse/Eclipse.java create mode 100644 src/core/lombok/eclipse/EclipseAST.java create mode 100644 src/core/lombok/eclipse/EclipseASTAdapter.java create mode 100644 src/core/lombok/eclipse/EclipseASTVisitor.java create mode 100644 src/core/lombok/eclipse/EclipseAnnotationHandler.java create mode 100644 src/core/lombok/eclipse/EclipseNode.java create mode 100644 src/core/lombok/eclipse/HandlerLibrary.java create mode 100644 src/core/lombok/eclipse/TransformEclipseAST.java create mode 100644 src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java create mode 100644 src/core/lombok/eclipse/handlers/HandleCleanup.java create mode 100644 src/core/lombok/eclipse/handlers/HandleData.java create mode 100644 src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java create mode 100644 src/core/lombok/eclipse/handlers/HandleGetter.java create mode 100644 src/core/lombok/eclipse/handlers/HandlePrintAST.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSetter.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSneakyThrows.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSynchronized.java create mode 100644 src/core/lombok/eclipse/handlers/HandleToString.java create mode 100644 src/core/lombok/eclipse/handlers/package-info.java create mode 100644 src/core/lombok/eclipse/package-info.java create mode 100644 src/core/lombok/javac/HandlerLibrary.java create mode 100644 src/core/lombok/javac/Javac.java create mode 100644 src/core/lombok/javac/JavacAST.java create mode 100644 src/core/lombok/javac/JavacASTAdapter.java create mode 100644 src/core/lombok/javac/JavacASTVisitor.java create mode 100644 src/core/lombok/javac/JavacAnnotationHandler.java create mode 100644 src/core/lombok/javac/JavacNode.java create mode 100644 src/core/lombok/javac/apt/Processor.java create mode 100644 src/core/lombok/javac/apt/package-info.java create mode 100644 src/core/lombok/javac/handlers/HandleCleanup.java create mode 100644 src/core/lombok/javac/handlers/HandleData.java create mode 100644 src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java create mode 100644 src/core/lombok/javac/handlers/HandleGetter.java create mode 100644 src/core/lombok/javac/handlers/HandlePrintAST.java create mode 100644 src/core/lombok/javac/handlers/HandleSetter.java create mode 100644 src/core/lombok/javac/handlers/HandleSneakyThrows.java create mode 100644 src/core/lombok/javac/handlers/HandleSynchronized.java create mode 100644 src/core/lombok/javac/handlers/HandleToString.java create mode 100644 src/core/lombok/javac/handlers/JavacHandlerUtil.java create mode 100644 src/core/lombok/javac/handlers/package-info.java create mode 100644 src/core/lombok/javac/package-info.java create mode 100644 src/core/lombok/package-info.java (limited to 'src/core') 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. + *

+ * Example: + *

+ * public void copyFile(String in, String out) throws IOException {
+ *     @Cleanup FileInputStream inStream = new FileInputStream(in);
+ *     @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);
+ *     }
+ * }
+ * 
+ * + * Will generate: + *
+ * public void copyFile(String in, String out) throws IOException {
+ *     @Cleanup FileInputStream inStream = new FileInputStream(in);
+ *     try {
+ *         @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();
+ *     }
+ * }
+ * 
+ * + * 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 _IF the main body + * throws an exception as well, as the earlier exception is usually far more useful. + *

+ * 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. + *

+ * 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. + *

+ * 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.
+ * Static fields are skipped (no getter or setter, and they are not included in toString, equals, hashCode, or the constructor). + *

+ * {@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: + * + *

+	 *     public @Data(staticConstructor = "of") class Point { final int x, y; }
+	 * 
+ * + * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 true 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 NOT 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. + *

+ * 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. + *

+ * 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. + * default: false + */ + 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: + *

+ *     private @Getter int foo;
+ * 
+ * + * will generate: + * + *
+ *     public int getFoo() {
+ *         return this.foo;
+ *     }
+ * 
+ * + * Note that fields of type {@code boolean} (but not {@code java.lang.Boolean}) will result in an + * {@code isFoo} name instead of {@code getFoo}. + *

+ * 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. + *

+ * Example usage: + *

+ *

public void run() {
+	 *     throw sneakyThrow(new IOException("You don't need to catch me!"));
+	 * }
+ *

+ * NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care + * about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception + * from the java compiler. + *

+ * 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 always throws an exception! + */ + public static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Lombok.sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } +} diff --git a/src/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 be deleted 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. + *

+ * Example: + *

+ *     private @Setter int foo;
+ * 
+ * + * will generate: + * + *
+ *     public void setFoo(int foo) {
+ *         this.foo = foo;
+ *     }
+ * 
+ * + * 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; + +/** + * @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. + *

+ * @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. + *

+ * You should use this annotation ONLY in the following two cases:

    + *
  1. 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:
    + * {@code IOException} in {@code ByteArrayOutputStream}
    + * {@code UnsupportedEncodingException} in new String(byteArray, "UTF-8").
  2. + *
  3. 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. + *

    + * Note that, as SneakyThrow is an implementation detail and NOT 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. + *

    + * WARNING: 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)}. + *

    + *

    + * Example: + *

    + * @SneakyThrows(UnsupportedEncodingException.class)
    + * public void utf8ToString(byte[] bytes) {
    + *     return new String(bytes, "UTF-8");
    + * }
    + * 
    + * + * {@code @SneakyThrows} without a parameter defaults to allowing every 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[] 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 false. + *

    + * You can also choose to include the result of {@code toString} in your class's superclass by setting the + * {@code callSuper} to true. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface ToString { + /** + * Include the name of each field when printing it. + * default: true + */ + 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. + *

    + * Mutually exclusive with {@link #exclude()}. + */ + String[] of() default {}; + + /** + * Include the result of the superclass's implementation of {@code toString} in the output. + * default: false + */ + 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, L extends LombokNode, 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 identityDetector = new IdentityHashMap(); + private Map nodeMap = new IdentityHashMap(); + + 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 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 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(); + nodeMap = new IdentityHashMap(); + } + + /** + * 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 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, Collection> fieldsOfASTClasses = new HashMap, Collection>(); + + /** 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 fieldsOf(Class c) { + Collection fields = fieldsOfASTClasses.get(c); + if (fields != null) return fields; + + fields = new ArrayList(); + getFields(c, fields); + fieldsOfASTClasses.put(c, fields); + return fields; + } + + private void getFields(Class c, Collection 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> getStatementTypes(); + + /** + * buildTree implementation that uses reflection to find all child nodes by way of inspecting + * the fields. */ + protected Collection buildWithField(Class nodeType, N statement, FieldAccess fa) { + List list = new ArrayList(); + 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)o, oldN, newN); + } + } + + return false; + } catch (IllegalAccessException e) { + throw sneakyThrow(e); + } + + } + + private boolean replaceStatementInCollection(Field field, Object fieldRef, List> 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> newChain = new ArrayList>(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> 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 nodeType, N child, FieldAccess fa, Collection 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 nodeType, Object array, Collection 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 nodeType, Object collection, Collection 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 { + private final Class type; + private final Map 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 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 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.
    + * For enums, supply the simple name part (everything after the last dot) as a string.
    + */ + 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 raws, List 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 type, Map 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