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 + .../lombok/eclipse/agent/EclipsePatcher.java | 215 +++++ .../lombok/eclipse/agent/PatchFixes.java | 67 ++ .../lombok/eclipse/agent/package-info.java | 26 + .../lombok/installer/AppleNativeLook.java | 43 + src/installer/lombok/installer/EclipseFinder.java | 325 ++++++++ .../lombok/installer/EclipseLocation.java | 474 +++++++++++ src/installer/lombok/installer/Installer.java | 895 +++++++++++++++++++++ .../lombok/installer/WindowsDriveInfo-i386.dll | Bin 0 -> 14472 bytes .../lombok/installer/WindowsDriveInfo-x86_64.dll | Bin 0 -> 66806 bytes .../lombok/installer/WindowsDriveInfo.java | 127 +++ src/installer/lombok/installer/loading.gif | Bin 0 -> 2248 bytes src/installer/lombok/installer/lombok.png | Bin 0 -> 24994 bytes src/installer/lombok/installer/lombok.svg | 181 +++++ src/installer/lombok/installer/lombokIcon.png | Bin 0 -> 788 bytes src/installer/lombok/installer/lombokText.png | Bin 0 -> 3055 bytes src/installer/lombok/installer/lombokText.svg | 67 ++ src/installer/lombok/installer/package-info.java | 28 + src/lombok/AccessLevel.java | 31 - src/lombok/Cleanup.java | 82 -- src/lombok/Data.java | 60 -- src/lombok/EqualsAndHashCode.java | 80 -- src/lombok/Getter.java | 58 -- src/lombok/Lombok.java | 60 -- src/lombok/NonNull.java | 43 - src/lombok/Setter.java | 55 -- src/lombok/SneakyThrows.java | 73 -- src/lombok/Synchronized.java | 46 -- src/lombok/ToString.java | 81 -- src/lombok/core/AST.java | 367 --------- src/lombok/core/AnnotationValues.java | 419 ---------- src/lombok/core/LombokNode.java | 297 ------- src/lombok/core/PrintAST.java | 51 -- src/lombok/core/SpiLoadUtil.java | 164 ---- src/lombok/core/TransformationsUtil.java | 149 ---- src/lombok/core/TypeLibrary.java | 79 -- src/lombok/core/TypeResolver.java | 114 --- src/lombok/core/Version.java | 48 -- src/lombok/core/package-info.java | 30 - src/lombok/eclipse/Eclipse.java | 479 ----------- src/lombok/eclipse/EclipseAST.java | 366 --------- src/lombok/eclipse/EclipseASTAdapter.java | 101 --- src/lombok/eclipse/EclipseASTVisitor.java | 295 ------- src/lombok/eclipse/EclipseAnnotationHandler.java | 54 -- src/lombok/eclipse/EclipseNode.java | 175 ---- src/lombok/eclipse/HandlerLibrary.java | 200 ----- src/lombok/eclipse/TransformEclipseAST.java | 196 ----- .../eclipse/handlers/EclipseHandlerUtil.java | 385 --------- src/lombok/eclipse/handlers/HandleCleanup.java | 200 ----- src/lombok/eclipse/handlers/HandleData.java | 243 ------ .../eclipse/handlers/HandleEqualsAndHashCode.java | 718 ----------------- src/lombok/eclipse/handlers/HandleGetter.java | 154 ---- src/lombok/eclipse/handlers/HandlePrintAST.java | 57 -- src/lombok/eclipse/handlers/HandleSetter.java | 172 ---- .../eclipse/handlers/HandleSneakyThrows.java | 224 ------ .../eclipse/handlers/HandleSynchronized.java | 132 --- src/lombok/eclipse/handlers/HandleToString.java | 304 ------- src/lombok/eclipse/handlers/package-info.java | 26 - src/lombok/eclipse/package-info.java | 26 - src/lombok/installer/AppleNativeLook.java | 43 - src/lombok/installer/EclipseFinder.java | 325 -------- src/lombok/installer/EclipseLocation.java | 474 ----------- src/lombok/installer/Installer.java | 895 --------------------- src/lombok/installer/WindowsDriveInfo-i386.dll | Bin 14472 -> 0 bytes src/lombok/installer/WindowsDriveInfo-x86_64.dll | Bin 66806 -> 0 bytes src/lombok/installer/WindowsDriveInfo.java | 127 --- src/lombok/installer/loading.gif | Bin 2248 -> 0 bytes src/lombok/installer/lombok.png | Bin 24994 -> 0 bytes src/lombok/installer/lombok.svg | 181 ----- src/lombok/installer/lombokIcon.png | Bin 788 -> 0 bytes src/lombok/installer/lombokText.png | Bin 3055 -> 0 bytes src/lombok/installer/lombokText.svg | 67 -- src/lombok/installer/package-info.java | 28 - src/lombok/javac/HandlerLibrary.java | 219 ----- src/lombok/javac/Javac.java | 162 ---- src/lombok/javac/JavacAST.java | 347 -------- src/lombok/javac/JavacASTAdapter.java | 98 --- src/lombok/javac/JavacASTVisitor.java | 266 ------ src/lombok/javac/JavacAnnotationHandler.java | 58 -- src/lombok/javac/JavacNode.java | 212 ----- src/lombok/javac/apt/Processor.java | 175 ---- src/lombok/javac/apt/package-info.java | 26 - src/lombok/javac/handlers/HandleCleanup.java | 147 ---- src/lombok/javac/handlers/HandleData.java | 186 ----- .../javac/handlers/HandleEqualsAndHashCode.java | 447 ---------- src/lombok/javac/handlers/HandleGetter.java | 143 ---- src/lombok/javac/handlers/HandlePrintAST.java | 57 -- src/lombok/javac/handlers/HandleSetter.java | 153 ---- src/lombok/javac/handlers/HandleSneakyThrows.java | 110 --- src/lombok/javac/handlers/HandleSynchronized.java | 102 --- src/lombok/javac/handlers/HandleToString.java | 237 ------ src/lombok/javac/handlers/JavacHandlerUtil.java | 335 -------- src/lombok/javac/handlers/package-info.java | 26 - src/lombok/javac/package-info.java | 26 - src/lombok/package-info.java | 27 - 157 files changed, 12901 insertions(+), 12593 deletions(-) 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 create mode 100644 src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java create mode 100644 src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java create mode 100644 src/eclipseAgent/lombok/eclipse/agent/package-info.java create mode 100644 src/installer/lombok/installer/AppleNativeLook.java create mode 100644 src/installer/lombok/installer/EclipseFinder.java create mode 100644 src/installer/lombok/installer/EclipseLocation.java create mode 100644 src/installer/lombok/installer/Installer.java create mode 100644 src/installer/lombok/installer/WindowsDriveInfo-i386.dll create mode 100644 src/installer/lombok/installer/WindowsDriveInfo-x86_64.dll create mode 100644 src/installer/lombok/installer/WindowsDriveInfo.java create mode 100644 src/installer/lombok/installer/loading.gif create mode 100644 src/installer/lombok/installer/lombok.png create mode 100644 src/installer/lombok/installer/lombok.svg create mode 100644 src/installer/lombok/installer/lombokIcon.png create mode 100644 src/installer/lombok/installer/lombokText.png create mode 100644 src/installer/lombok/installer/lombokText.svg create mode 100644 src/installer/lombok/installer/package-info.java delete mode 100644 src/lombok/AccessLevel.java delete mode 100644 src/lombok/Cleanup.java delete mode 100644 src/lombok/Data.java delete mode 100644 src/lombok/EqualsAndHashCode.java delete mode 100644 src/lombok/Getter.java delete mode 100644 src/lombok/Lombok.java delete mode 100644 src/lombok/NonNull.java delete mode 100644 src/lombok/Setter.java delete mode 100644 src/lombok/SneakyThrows.java delete mode 100644 src/lombok/Synchronized.java delete mode 100644 src/lombok/ToString.java delete mode 100644 src/lombok/core/AST.java delete mode 100644 src/lombok/core/AnnotationValues.java delete mode 100644 src/lombok/core/LombokNode.java delete mode 100644 src/lombok/core/PrintAST.java delete mode 100644 src/lombok/core/SpiLoadUtil.java delete mode 100644 src/lombok/core/TransformationsUtil.java delete mode 100644 src/lombok/core/TypeLibrary.java delete mode 100644 src/lombok/core/TypeResolver.java delete mode 100644 src/lombok/core/Version.java delete mode 100644 src/lombok/core/package-info.java delete mode 100644 src/lombok/eclipse/Eclipse.java delete mode 100644 src/lombok/eclipse/EclipseAST.java delete mode 100644 src/lombok/eclipse/EclipseASTAdapter.java delete mode 100644 src/lombok/eclipse/EclipseASTVisitor.java delete mode 100644 src/lombok/eclipse/EclipseAnnotationHandler.java delete mode 100644 src/lombok/eclipse/EclipseNode.java delete mode 100644 src/lombok/eclipse/HandlerLibrary.java delete mode 100644 src/lombok/eclipse/TransformEclipseAST.java delete mode 100644 src/lombok/eclipse/handlers/EclipseHandlerUtil.java delete mode 100644 src/lombok/eclipse/handlers/HandleCleanup.java delete mode 100644 src/lombok/eclipse/handlers/HandleData.java delete mode 100644 src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java delete mode 100644 src/lombok/eclipse/handlers/HandleGetter.java delete mode 100644 src/lombok/eclipse/handlers/HandlePrintAST.java delete mode 100644 src/lombok/eclipse/handlers/HandleSetter.java delete mode 100644 src/lombok/eclipse/handlers/HandleSneakyThrows.java delete mode 100644 src/lombok/eclipse/handlers/HandleSynchronized.java delete mode 100644 src/lombok/eclipse/handlers/HandleToString.java delete mode 100644 src/lombok/eclipse/handlers/package-info.java delete mode 100644 src/lombok/eclipse/package-info.java delete mode 100644 src/lombok/installer/AppleNativeLook.java delete mode 100644 src/lombok/installer/EclipseFinder.java delete mode 100644 src/lombok/installer/EclipseLocation.java delete mode 100644 src/lombok/installer/Installer.java delete mode 100644 src/lombok/installer/WindowsDriveInfo-i386.dll delete mode 100644 src/lombok/installer/WindowsDriveInfo-x86_64.dll delete mode 100644 src/lombok/installer/WindowsDriveInfo.java delete mode 100644 src/lombok/installer/loading.gif delete mode 100644 src/lombok/installer/lombok.png delete mode 100644 src/lombok/installer/lombok.svg delete mode 100644 src/lombok/installer/lombokIcon.png delete mode 100644 src/lombok/installer/lombokText.png delete mode 100644 src/lombok/installer/lombokText.svg delete mode 100644 src/lombok/installer/package-info.java delete mode 100644 src/lombok/javac/HandlerLibrary.java delete mode 100644 src/lombok/javac/Javac.java delete mode 100644 src/lombok/javac/JavacAST.java delete mode 100644 src/lombok/javac/JavacASTAdapter.java delete mode 100644 src/lombok/javac/JavacASTVisitor.java delete mode 100644 src/lombok/javac/JavacAnnotationHandler.java delete mode 100644 src/lombok/javac/JavacNode.java delete mode 100644 src/lombok/javac/apt/Processor.java delete mode 100644 src/lombok/javac/apt/package-info.java delete mode 100644 src/lombok/javac/handlers/HandleCleanup.java delete mode 100644 src/lombok/javac/handlers/HandleData.java delete mode 100644 src/lombok/javac/handlers/HandleEqualsAndHashCode.java delete mode 100644 src/lombok/javac/handlers/HandleGetter.java delete mode 100644 src/lombok/javac/handlers/HandlePrintAST.java delete mode 100644 src/lombok/javac/handlers/HandleSetter.java delete mode 100644 src/lombok/javac/handlers/HandleSneakyThrows.java delete mode 100644 src/lombok/javac/handlers/HandleSynchronized.java delete mode 100644 src/lombok/javac/handlers/HandleToString.java delete mode 100644 src/lombok/javac/handlers/JavacHandlerUtil.java delete mode 100644 src/lombok/javac/handlers/package-info.java delete mode 100644 src/lombok/javac/package-info.java delete mode 100644 src/lombok/package-info.java (limited to 'src') 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 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 getRawExpressions(String annotationMethodName) { + AnnotationValue v = values.get(annotationMethodName); + return v == null ? Collections.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 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 getProbableFQTypes(String annotationMethodName) { + List result = new ArrayList(); + 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 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, L extends LombokNode, N> { + protected final A ast; + protected final Kind kind; + protected final N node; + protected final List 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 children, Kind kind) { + this.ast = ast; + this.kind = kind; + this.node = node; + this.children = children == null ? new ArrayList() : 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 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 down() { + return new ArrayList(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 all children. + */ + public void rebuild() { + Map oldNodes = new IdentityHashMap(); + gatherAndRemoveChildren(oldNodes); + + L newNode = ast.buildTree(get(), kind); + + ast.replaceNewWithExistingOld(oldNodes, newNode); + } + + @SuppressWarnings("unchecked") + private void gatherAndRemoveChildren(Map 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 Iterable findServices(Class 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 Iterable findServices(final Class target, final ClassLoader loader) throws IOException { + Enumeration resources = loader.getResources("META-INF/services/" + target.getName()); + final Set entries = new LinkedHashSet(); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + readServicesFromUrl(entries, url); + } + + final Iterator names = entries.iterator(); + return new Iterable () { + @Override public Iterator iterator() { + return new Iterator() { + @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 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}. + * + * 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 findAnnotationClass(Class c, Class base) { + if (c == Object.class || c == null) return null; + for (Type iface : c.getGenericInterfaces()) { + if (iface instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType)iface; + if (!base.equals(p.getRawType())) continue; + Type target = p.getActualTypeArguments()[0]; + if (target instanceof Class) { + if (Annotation.class.isAssignableFrom((Class) target)) { + return (Class) target; + } + } + + throw new ClassCastException("Not an annotation type: " + target); + } + } + + Class potential = findAnnotationClass(c.getSuperclass(), base); + if (potential != null) return potential; + for (Class iface : c.getInterfaces()) { + potential = findAnnotationClass(iface, base); + if (potential != null) return potential; + } + + return null; + } +} diff --git a/src/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 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 + * not 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 toAllGetterNames(CharSequence fieldName, boolean isBoolean) { + if (!isBoolean) return Collections.singletonList(toGetterName(fieldName, false)); + + List baseNames = new ArrayList(); + 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 names = new HashSet(); + 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(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> simpleToQualifiedMap = new HashMap>(); + + /** + * 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 existing = simpleToQualifiedMap.get(keyName); + Set set = (existing == null) ? new HashSet() : new HashSet(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 findCompatible(String typeReference) { + Set result = simpleToQualifiedMap.get(typeReference); + return result == null ? Collections.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 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 importStrings) { + this.library = library; + this.imports = makeImportList(packageString, importStrings); + } + + private static Collection makeImportList(String packageString, Collection importStrings) { + Set imports = new HashSet(); + if (packageString != null) imports.add(packageString + ".*"); + imports.addAll(importStrings == null ? Collections.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 findTypeMatches(LombokNode context, String typeRef) { + Collection 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 eliminateImpossibleMatches(Collection potentialMatches) { + Set results = new HashSet(); + + for (String importedType : imports) { + Collection reduced = new HashSet(library.findCompatible(importedType)); + reduced.retainAll(potentialMatches); + results.addAll(reduced); + } + + return results; + } + + private boolean nameConflictInImportList(String simpleName, Collection 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 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 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 AnnotationValues + createAnnotation(Class type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); + Map values = new HashMap(); + + final MemberValuePair[] pairs = annotation.memberValuePairs(); + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + List raws = new ArrayList(); + List guesses = new ArrayList(); + 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(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 { + /** + * 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 getImportStatements() { + List imports = new ArrayList(); + 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 queuedProblems = new ArrayList(); + + 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 children = buildTypes(top.types); + return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT)); + } + + private void addIfNotNull(Collection collection, EclipseNode n) { + if (n != null) collection.add(n); + } + + private List buildTypes(TypeDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type)); + return childNodes; + } + + private EclipseNode buildType(TypeDeclaration type) { + if (setAndGetAsHandled(type)) return null; + List childNodes = new ArrayList(); + 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 buildFields(FieldDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child)); + return childNodes; + } + + private static List singleton(T item) { + List list = new ArrayList(); + 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 childNodes = new ArrayList(); + 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 buildMethods(AbstractMethodDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method)); + return childNodes; + } + + private EclipseNode buildMethod(AbstractMethodDeclaration method) { + if (setAndGetAsHandled(method)) return null; + List childNodes = new ArrayList(); + 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 buildArguments(Argument[] children) { + List childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + addIfNotNull(childNodes, buildStatement(local.initialization)); + childNodes.addAll(buildAnnotations(local.annotations)); + return putInMap(new EclipseNode(this, local, childNodes, kind)); + } + + private Collection buildAnnotations(Annotation[] annotations) { + List elements = new ArrayList(); + 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 buildStatements(Statement[] children) { + List childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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> getStatementTypes() { + return Collections.>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("", node.getFileName(), Eclipse.isGenerated(unit) ? " (GENERATED)" : ""); + indent++; + } + + public void endVisitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) { + indent--; + print(""); + } + + public void visitType(EclipseNode node, TypeDeclaration type) { + print("", str(type.name), Eclipse.isGenerated(type) ? " (GENERATED)" : ""); + indent++; + if (printContent) { + print("%s", type); + disablePrinting++; + } + } + + public void visitAnnotationOnType(TypeDeclaration type, EclipseNode node, Annotation annotation) { + forcePrint("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitType(EclipseNode node, TypeDeclaration type) { + if (printContent) disablePrinting--; + indent--; + print("", 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("", (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance"); + } + + public void visitField(EclipseNode node, FieldDeclaration field) { + print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitField(EclipseNode node, FieldDeclaration field) { + if (printContent) disablePrinting--; + indent--; + print("", 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("", Eclipse.isGenerated(method) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethod(EclipseNode node, AbstractMethodDeclaration method) { + if (printContent) disablePrinting--; + String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD"; + indent--; + print("", type, str(method.selector)); + } + + public void visitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + indent--; + print("", str(arg.type), str(arg.name)); + } + + public void visitLocal(EclipseNode node, LocalDeclaration local) { + print("", Eclipse.isGenerated(local) ? " (GENERATED)" : "", str(local.type), str(local.name), local.initialization); + indent++; + } + + public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode node, Annotation annotation) { + print("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitLocal(EclipseNode node, LocalDeclaration local) { + indent--; + print("", 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("", 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} + * + * 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 { + /** + * 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 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 { + /** {@inheritDoc} */ + EclipseNode(EclipseAST ast, ASTNode node, List 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 { + private EclipseAnnotationHandler handler; + private Class annotationClass; + + AnnotationHandlerContainer(EclipseAnnotationHandler handler, Class annotationClass) { + this.handler = handler; + this.annotationClass = annotationClass; + } + + public boolean handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation, + final EclipseNode annotationNode) { + AnnotationValues annValues = Eclipse.createAnnotation(annotationClass, annotationNode); + return handler.handle(annValues, annotation, annotationNode); + } + } + + private Map> annotationHandlers = + new HashMap>(); + + private Collection visitorHandlers = new ArrayList(); + + 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 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 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 result = new ArrayList(); + 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 createListOfNonExistentFields(List 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 problematic = new ArrayList(); + 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 { + public boolean handle(AnnotationValues 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 { + public boolean handle(AnnotationValues 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 nodesForConstructor = new ArrayList(); + 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 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 args = new ArrayList(); + List assigns = new ArrayList(); + List nullChecks = new ArrayList(); + + 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 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 args = new ArrayList(); + List assigns = new ArrayList(); + 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 { + private static final Set BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(Arrays.asList( + "byte", "short", "int", "long", "char", "boolean", "double", "float"))); + + private void checkForBogusFieldNames(EclipseNode type, AnnotationValues 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 annotation, + Annotation ast, EclipseNode annotationNode) { + EqualsAndHashCode ann = annotation.getInstance(); + List excludes = Arrays.asList(ann.exclude()); + List 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 excludes, List 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 nodesForEquality = new ArrayList(); + 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 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 statements = new ArrayList(); + List intoResult = new ArrayList(); + + 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 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 statements = new ArrayList(); + + /* 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 { + /** + * 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 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 { + public boolean handle(AnnotationValues 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 { + /** + * 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 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 { + 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 annotation, Annotation source, EclipseNode annotationNode) { + List exceptionNames = annotation.getRawExpressions("value"); + List exceptions = new ArrayList(); + + 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 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 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 { + private static final char[] INSTANCE_LOCK_NAME = "$lock".toCharArray(); + private static final char[] STATIC_LOCK_NAME = "$LOCK".toCharArray(); + + @Override public boolean handle(AnnotationValues 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 { + private void checkForBogusFieldNames(EclipseNode type, AnnotationValues 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 annotation, Annotation ast, EclipseNode annotationNode) { + ToString ann = annotation.getInstance(); + List excludes = Arrays.asList(ann.exclude()); + List 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 excludes, List 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 nodesForToString = new ArrayList(); + 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 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 BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(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> annotationHandlers = new HashMap>(); + private final Collection visitorHandlers = new ArrayList(); + 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 { + private JavacAnnotationHandler handler; + private Class annotationClass; + + AnnotationHandlerContainer(JavacAnnotationHandler handler, Class 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 it = ServiceLoader.load(JavacAnnotationHandler.class, + JavacAnnotationHandler.class.getClassLoader()).iterator(); + while (it.hasNext()) { + try { + JavacAnnotationHandler handler = it.next(); + Class annotationClass = + SpiLoadUtil.findAnnotationClass(handler.getClass(), JavacAnnotationHandler.class); + AnnotationHandlerContainer container = new AnnotationHandlerContainer(handler, annotationClass); + if (lib.annotationHandlers.put(container.annotationClass.getName(), container) != null) { + 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 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 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 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 AnnotationValues createAnnotation(Class type, final JavacNode node) { + Map values = new HashMap(); + JCAnnotation anno = (JCAnnotation) node.get(); + List arguments = anno.getArguments(); + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + List raws = new ArrayList(); + List guesses = new ArrayList(); + final List positions = new ArrayList(); + 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 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(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 { + 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 getImportStatements() { + List imports = new ArrayList(); + 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(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 childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + + 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 childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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> getStatementTypes() { + Collection> collection = new ArrayList>(2); + collection.add(JCStatement.class); + collection.add(JCExpression.class); + return collection; + } + + private static void addIfNotNull(Collection 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> 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> 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> reducedChain = new ArrayList>(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.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("", LombokNode.getFileName()); + indent++; + } + + @Override public void endVisitCompilationUnit(JavacNode node, JCCompilationUnit unit) { + indent--; + print(""); + } + + @Override public void visitType(JavacNode node, JCClassDecl type) { + print("", type.name); + indent++; + if (printContent) { + print("%s", type); + disablePrinting++; + } + } + + @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode node, JCAnnotation annotation) { + forcePrint("", annotation); + } + + @Override public void endVisitType(JavacNode node, JCClassDecl type) { + if (printContent) disablePrinting--; + indent--; + print("", 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("", initializer.isStatic() ? "static" : "instance"); + } + + @Override public void visitField(JavacNode node, JCVariableDecl field) { + print("", 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); + } + + @Override public void endVisitField(JavacNode node, JCVariableDecl field) { + if (printContent) disablePrinting--; + indent--; + print("", field.vartype, field.name); + } + + @Override public void visitMethod(JavacNode node, JCMethodDecl method) { + final String type; + if (method.name.contentEquals("")) { + 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); + } + + @Override public void endVisitMethod(JavacNode node, JCMethodDecl method) { + if (printContent) disablePrinting--; + indent--; + print("", "XMETHOD", method.name); + } + + @Override public void visitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) { + print("", arg.vartype, arg.name); + indent++; + } + + @Override public void visitAnnotationOnMethodArgument(JCVariableDecl arg, JCMethodDecl method, JavacNode nodeAnnotation, JCAnnotation annotation) { + forcePrint("", annotation); + } + + @Override public void endVisitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) { + indent--; + print("", arg.vartype, arg.name); + } + + @Override public void visitLocal(JavacNode node, JCVariableDecl local) { + print("", local.vartype, local.name); + indent++; + } + + @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode node, JCAnnotation annotation) { + print("", annotation); + } + + @Override public void endVisitLocal(JavacNode node, JCVariableDecl local) { + indent--; + print("", 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("", 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} + * + * 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 { + /** + * 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 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 { + /** + * Passes through to the parent constructor. + */ + public JavacNode(JavacAST ast, JCTree node, List 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 annotations, RoundEnvironment roundEnv) { + if (processingEnv == null) { + if (errorToShow != null) { + Set rootElements = roundEnv.getRootElements(); + if (!rootElements.isEmpty()) { + rawProcessingEnv.getMessager().printMessage(Kind.WARNING, errorToShow, rootElements.iterator().next()); + errorToShow = null; + } + } + return false; + } + + IdentityHashMap units = new IdentityHashMap(); + for (Element element : roundEnv.getRootElements()) { + JCCompilationUnit unit = toUnit(element); + if (unit != null) units.put(unit, null); + } + + List asts = new ArrayList(); + + 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 { + @Override public boolean handle(AnnotationValues 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 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 tryBlock = List.nil(); + List 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 finalizerBlock = List.of(maker.Exec( + maker.Apply(List.nil(), cleanupCall, List.nil()))); + + JCBlock finalizer = maker.Block(0, finalizerBlock); + newStatements = newStatements.append(maker.Try(maker.Block(0, tryBlock), List.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 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 { + @Override public boolean handle(AnnotationValues 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 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 fields) { + TreeMaker maker = typeNode.getTreeMaker(); + JCClassDecl type = (JCClassDecl) typeNode.get(); + + List nullChecks = List.nil(); + List assigns = List.nil(); + List params = List.nil(); + + for (JavacNode fieldNode : fields) { + JCVariableDecl field = (JCVariableDecl) fieldNode.get(); + List nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN); + List 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(""), + null, type.typarams, params, List.nil(), maker.Block(0L, nullChecks.appendList(assigns)), null); + } + + private JCMethodDecl createStaticConstructor(String name, JavacNode typeNode, List fields) { + TreeMaker maker = typeNode.getTreeMaker(); + JCClassDecl type = (JCClassDecl) typeNode.get(); + + JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PUBLIC); + + JCExpression returnType, constructorType; + + List typeParams = List.nil(); + List params = List.nil(); + List typeArgs1 = List.nil(); + List typeArgs2 = List.nil(); + List 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 tArgs = List.nil(); + for (JCExpression arg : typeApply.arguments) tArgs = tArgs.append(arg); + pType = maker.TypeApply(typeApply.clazz, tArgs); + } else { + pType = field.vartype; + } + List nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN); + List 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.nil(), constructorType, args, null)); + JCBlock body = maker.Block(0, List.of(returnStatement)); + + return maker.MethodDef(mods, typeNode.toName(name), returnType, typeParams, params, List.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 { + private void checkForBogusFieldNames(JavacNode type, AnnotationValues 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 annotation, JCAnnotation ast, JavacNode annotationNode) { + EqualsAndHashCode ann = annotation.getInstance(); + List excludes = List.from(ann.exclude()); + List 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 excludes, List 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 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 fields, boolean callSuper) { + TreeMaker maker = typeNode.getTreeMaker(); + + JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.nil()); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); + JCExpression returnType = maker.TypeIdent(TypeTags.INT); + List 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 intoResult = List.nil(); + + if (callSuper) { + JCMethodInvocation callToSuper = maker.Apply(List.nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), + List.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.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.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.nil(), hcMethod, List.of(thisDotField))); + } else /* objects */ { + /* this.fieldName == null ? 0 : this.fieldName.hashCode() */ + JCExpression hcCall = maker.Apply(List.nil(), maker.Select(thisDotField, typeNode.toName("hashCode")), + List.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.nil(), List.nil(), List.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 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.nil()); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); + JCExpression objectType = maker.Type(typeNode.getSymbolTable().objectType); + JCExpression returnType = maker.TypeIdent(TypeTags.BOOLEAN); + + List statements = List.nil(); + List 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 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.nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), + List.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 wildcards1 = List.nil(); + List 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 args = List.of(thisDotField, otherDotField); + statements = statements.append(maker.If(maker.Unary(JCTree.NOT, + maker.Apply(List.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.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.nil(), params, List.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 args = List.of(thisDotField, otherDotField); + JCBinary compareCallEquals0 = maker.Binary(JCTree.NE, maker.Apply( + List.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 { + /** + * 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 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 methodGenericParams = List.nil(); + List parameters = List.nil(); + List throwsClauses = List.nil(); + JCExpression annotationMethodDefaultValue = null; + + List nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); + List 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 { + @Override public boolean handle(AnnotationValues 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 { + /** + * 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 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 statements; + List nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); + List nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN); + + if (nonNulls.isEmpty()) { + statements = List.of(treeMaker.Exec(assign)); + } else { + JCStatement nullCheck = generateNullCheck(treeMaker, field); + if (nullCheck != null) statements = List.of(nullCheck, treeMaker.Exec(assign)); + else statements = List.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 methodGenericParams = List.nil(); + List parameters = List.of(param); + List throwsClauses = List.nil(); + JCExpression annotationMethodDefaultValue = null; + + return treeMaker.MethodDef(treeMaker.Modifiers(access, List.nil()), methodName, methodType, + methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); + } +} diff --git a/src/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 { + @Override public boolean handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + Collection exceptionNames = annotation.getRawExpressions("value"); + + List memberValuePairs = ast.getArguments(); + if (memberValuePairs == null || memberValuePairs.size() == 0) return false; + + java.util.List exceptions = new ArrayList(); + 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 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 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 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.of(maker.Throw(maker.Apply( + List.nil(), lombokLombokSneakyThrowNameRef, + List.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 { + private static final String INSTANCE_LOCK_NAME = "$lock"; + private static final String STATIC_LOCK_NAME = "$LOCK"; + + @Override public boolean handle(AnnotationValues 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.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.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 { + private void checkForBogusFieldNames(JavacNode type, AnnotationValues 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 annotation, JCAnnotation ast, JavacNode annotationNode) { + ToString ann = annotation.getInstance(); + List excludes = List.from(ann.exclude()); + List 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 excludes, List 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 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 fields, boolean includeFieldNames, boolean callSuper) { + TreeMaker maker = typeNode.getTreeMaker(); + + JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.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.nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("toString")), + List.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.nil(), hcMethod, List.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.nil(), List.nil(), List.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 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("")) { + 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("")) { + //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 addAllButOne(List defs, int idx) { + List 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 findAnnotations(JavacNode fieldNode, Pattern namePattern) { + List 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.nil(), npe, List.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 createListOfNonExistentFields(List 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 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; diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java new file mode 100644 index 00000000..7d2a28bc --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -0,0 +1,215 @@ +/* + * 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.agent; + +import java.lang.instrument.Instrumentation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.TargetMatcher; +import lombok.patcher.equinox.EquinoxClassLoader; +import lombok.patcher.scripts.ScriptBuilder; + +/** + * This is a java-agent that patches some of eclipse's classes so AST Nodes are handed off to Lombok + * for modification before Eclipse actually uses them to compile, render errors, show code outlines, + * create auto-completion dialogs, and anything else eclipse does with java code. See the *Transformer + * classes in this package for more information about which classes are transformed and how they are + * transformed. + */ +public class EclipsePatcher { + private EclipsePatcher() {} + + public static void agentmain(@SuppressWarnings("unused") String agentArgs, Instrumentation instrumentation) throws Exception { + registerPatchScripts(instrumentation, true); + } + + public static void premain(@SuppressWarnings("unused") String agentArgs, Instrumentation instrumentation) throws Exception { + registerPatchScripts(instrumentation, false); + } + + private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses) { + ScriptManager sm = new ScriptManager(); + sm.registerTransformer(instrumentation); + EquinoxClassLoader.addPrefix("lombok."); + EquinoxClassLoader.registerScripts(sm); + + patchLombokizeAST(sm); + patchAvoidReparsingGeneratedCode(sm); + patchCatchReparse(sm); + patchSetGeneratedFlag(sm); + patchHideGeneratedNodes(sm); + + if (reloadExistingClasses) sm.reloadClasses(instrumentation); + } + + private static void patchHideGeneratedNodes(ScriptManager sm) { + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByNode")) + .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByBinding")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "removeGeneratedSimpleNames", + "([Lorg/eclipse/jdt/core/dom/SimpleName;)[Lorg/eclipse/jdt/core/dom/SimpleName;")) + .request(StackRequest.RETURN_VALUE).build()); + + patchRefactorScripts(sm); + patchFormatters(sm); + } + + private static void patchFormatters(ScriptManager sm) { + sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.ui.text.java.JavaFormattingStrategy", "format", "void")) + .callToWrap(new Hook("org/eclipse/jdt/internal/corext/util/CodeFormatterUtil", "reformat", + "(ILjava/lang/String;IIILjava/lang/String;Ljava/util/Map;)Lorg/eclipse/text/edits/TextEdit;")) + .symbol("lombok.disable").build()); + } + + private static void patchRefactorScripts(ScriptManager sm) { + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "replace")) + .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "remove")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "skipRewritingGeneratedNodes", + "(Lorg/eclipse/jdt/core/dom/ASTNode;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor", "addConstructorRenames")) + .methodToWrap(new Hook("org/eclipse/jdt/core/IType", "getMethods", "()[Lorg/eclipse/jdt/core/IMethod;")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "removeGeneratedMethods", + "([Lorg/eclipse/jdt/core/IMethod;)[Lorg/eclipse/jdt/core/IMethod;")) + .transplant().build()); + } + + private static void patchCatchReparse(ScriptManager sm) { + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveStartingCatchPosition")) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "fixRetrieveStartingCatchPosition", "(I)I")) + .transplant().request(StackRequest.PARAM1).build()); + } + + private static void patchSetGeneratedFlag(ScriptManager sm) { + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.internal.compiler.ast.ASTNode") + .fieldName("$generatedBy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic().setTransient().build()); + + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.core.dom.ASTNode") + .fieldName("$isGenerated").fieldType("Z") + .setPublic().setTransient().build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new TargetMatcher() { + @Override public boolean matches(String classSpec, String methodName, String descriptor) { + if (!"convert".equals(methodName)) return false; + + List fullDesc = MethodTarget.decomposeFullDesc(descriptor); + if ("V".equals(fullDesc.get(0))) return false; + if (fullDesc.size() < 2) return false; + if (!fullDesc.get(1).startsWith("Lorg/eclipse/jdt/internal/compiler/ast/")) return false; + return true; + } + + @Override public Collection getAffectedClasses() { + return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); + } + }).request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "setIsGeneratedFlag", + "(Lorg/eclipse/jdt/core/dom/ASTNode;Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;)V")) + .transplant().build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new TargetMatcher() { + @Override public boolean matches(String classSpec, String methodName, String descriptor) { + if (!methodName.startsWith("convert")) return false; + + List fullDesc = MethodTarget.decomposeFullDesc(descriptor); + if (fullDesc.size() < 2) return false; + if (!fullDesc.get(1).startsWith("Lorg/eclipse/jdt/internal/compiler/ast/")) return false; + + return true; + } + + @Override public Collection getAffectedClasses() { + return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); + } + }).methodToWrap(new Hook("org/eclipse/jdt/core/dom/SimpleName", "", "(Lorg/eclipse/jdt/core/dom/AST;)V")) + .requestExtra(StackRequest.PARAM1) + .wrapMethod(new Hook("lombok/eclipse/agent/PatchFixes", "setIsGeneratedFlagForSimpleName", + "(Lorg/eclipse/jdt/core/dom/SimpleName;Ljava/lang/Object;)V")) + .transplant().build()); + } + + private static void patchAvoidReparsingGeneratedCode(ScriptManager sm) { + final String PARSER_SIG1 = "org.eclipse.jdt.internal.compiler.parser.Parser"; + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.MethodDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "boolean")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(PARSER_SIG1, "parse", "void", + "org.eclipse.jdt.internal.compiler.ast.Initializer", + "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", + "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) + .decisionMethod(new Hook("lombok/eclipse/agent/PatchFixes", "checkBit24", "(Ljava/lang/Object;)Z")) + .transplant().request(StackRequest.PARAM1).build()); + } + + private static void patchLombokizeAST(ScriptManager sm) { + sm.addScript(ScriptBuilder.addField() + .targetClass("org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration") + .fieldName("$lombokAST").fieldType("Ljava/lang/Object;") + .setPublic().setTransient().build()); + + final String PARSER_SIG1 = "org.eclipse.jdt.internal.compiler.parser.Parser"; + final String PARSER_SIG2 = "Lorg/eclipse/jdt/internal/compiler/parser/Parser;"; + final String CUD_SIG1 = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + final String CUD_SIG2 = "Lorg/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration;"; + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG1, "getMethodBodies", "void", CUD_SIG1)) + .wrapMethod(new Hook("lombok/eclipse/TransformEclipseAST", "transform", + "(" + PARSER_SIG2 + CUD_SIG2 + ")V")) + .request(StackRequest.THIS, StackRequest.PARAM1).build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG1, "endParse", CUD_SIG1, "int")) + .wrapMethod(new Hook("lombok/eclipse/TransformEclipseAST", "transform_swapped", + "(" + CUD_SIG2 + PARSER_SIG2 + ")V")) + .request(StackRequest.THIS, StackRequest.RETURN_VALUE).build()); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java new file mode 100644 index 00000000..5d54692e --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -0,0 +1,67 @@ +package lombok.eclipse.agent; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.SimpleName; + +public class PatchFixes { + public static int fixRetrieveStartingCatchPosition(int in) { + return in; + } + + private static final int BIT24 = 0x800000; + + public static boolean checkBit24(Object node) throws Exception { + int bits = (Integer)(node.getClass().getField("bits").get(node)); + return (bits & BIT24) != 0; + } + + public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { + return ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); + } + + public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, + org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { + boolean isGenerated = internalNode.getClass().getField("$generatedBy").get(internalNode) != null; + if (isGenerated) { + domNode.getClass().getField("$isGenerated").set(domNode, true); + domNode.setFlags(domNode.getFlags() & ~ASTNode.ORIGINAL); + } + } + + public static void setIsGeneratedFlagForSimpleName(SimpleName name, Object internalNode) throws Exception { + if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { + if (internalNode.getClass().getField("$generatedBy").get(internalNode) != null) { + name.getClass().getField("$isGenerated").set(name, true); + } + } + } + + public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { + List result = new ArrayList(); + for (IMethod m : methods) { + if (m.getNameRange().getLength() > 0) result.add(m); + } + return result.size() == methods.length ? methods : result.toArray(new IMethod[0]); + } + + public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { + Field f = SimpleName.class.getField("$isGenerated"); + + int count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; + } + if (count == in.length) return in; + SimpleName[] newSimpleNames = new SimpleName[count]; + count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; + } + return newSimpleNames; + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/package-info.java b/src/eclipseAgent/lombok/eclipse/agent/package-info.java new file mode 100644 index 00000000..12255f81 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/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 eclipse by being loaded as a javaagent. + */ +package lombok.eclipse.agent; diff --git a/src/installer/lombok/installer/AppleNativeLook.java b/src/installer/lombok/installer/AppleNativeLook.java new file mode 100644 index 00000000..6e64032e --- /dev/null +++ b/src/installer/lombok/installer/AppleNativeLook.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.installer; + +import java.awt.Image; +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; + +/** + * Mac OS X specific code to gussy up the GUI a little bit, mostly with a nice dock icon. Well, nicer than + * the standard icon, at any rate. + */ +class AppleNativeLook { + public static void go() throws Exception { + Class appClass = Class.forName("com.apple.eawt.Application"); + Object app = appClass.getMethod("getApplication").invoke(null); + appClass.getMethod("removeAboutMenuItem").invoke(app); + appClass.getMethod("removePreferencesMenuItem").invoke(app); + + BufferedImage image = ImageIO.read(AppleNativeLook.class.getResource("lombokIcon.png")); + appClass.getMethod("setDockIconImage", Image.class).invoke(app, image); + } +} diff --git a/src/installer/lombok/installer/EclipseFinder.java b/src/installer/lombok/installer/EclipseFinder.java new file mode 100644 index 00000000..8e45852c --- /dev/null +++ b/src/installer/lombok/installer/EclipseFinder.java @@ -0,0 +1,325 @@ +/* + * 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.installer; + +import static java.util.Arrays.asList; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.Lombok; +import lombok.core.Version; +import lombok.installer.EclipseLocation.NotAnEclipseException; + +/** Utility class for doing various OS-specific operations related to finding Eclipse installations. */ +class EclipseFinder { + private EclipseFinder() { + //Prevent instantiation. + } + + /** + * Returns a File object pointing to our own jar file. Will obviously fail if the installer was started via + * a jar that wasn't accessed via the file-system, or if its started via e.g. unpacking the jar. + */ + static File findOurJar() { + try { + URI uri = EclipseFinder.class.getResource("/" + EclipseFinder.class.getName().replace('.', '/') + ".class").toURI(); + Pattern p = Pattern.compile("^jar:file:([^\\!]+)\\!.*\\.class$"); + Matcher m = p.matcher(uri.toString()); + if (!m.matches()) return new File("lombok.jar"); + String rawUri = m.group(1); + return new File(URLDecoder.decode(rawUri, Charset.defaultCharset().name())); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + private static final AtomicBoolean windowsDriveInfoLibLoaded = new AtomicBoolean(false); + private static void loadWindowsDriveInfoLib() throws IOException { + if (!windowsDriveInfoLibLoaded.compareAndSet(false, true)) return; + + final String prefix = "lombok-" + Version.getVersion() + "-"; + + File temp = File.createTempFile("lombok", ".mark"); + File dll1 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-i386.dll"); + File dll2 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-x86_64.dll"); + temp.delete(); + dll1.deleteOnExit(); + dll2.deleteOnExit(); + try { + if (unpackDLL("WindowsDriveInfo-i386.dll", dll1)) { + System.load(dll1.getAbsolutePath()); + return; + } + } catch (Throwable ignore) {} + + try { + if (unpackDLL("WindowsDriveInfo-x86_64.dll", dll2)) { + System.load(dll2.getAbsolutePath()); + } + } catch (Throwable ignore) {} + } + + private static boolean unpackDLL(String dllName, File target) throws IOException { + InputStream in = EclipseFinder.class.getResourceAsStream(dllName); + try { + try { + FileOutputStream out = new FileOutputStream(target); + try { + byte[] b = new byte[32000]; + while (true) { + int r = in.read(b); + if (r == -1) break; + out.write(b, 0, r); + } + } finally { + out.close(); + } + } catch (IOException e) { + //Fall through - if there is a file named lombok-WindowsDriveInfo-arch.dll, we'll try it. + return target.exists() && target.canRead(); + } + } finally { + in.close(); + } + + return true; + } + + /** + * Returns all drive letters on windows, regardless of what kind of drive is represented. + * + * @return A List of drive letters, such as ["A", "C", "D", "X"]. + */ + static List getDrivesOnWindows() throws Throwable { + loadWindowsDriveInfoLib(); + + List drives = new ArrayList(); + + WindowsDriveInfo info = new WindowsDriveInfo(); + for (String drive : info.getLogicalDrives()) { + if (info.isFixedDisk(drive)) drives.add(drive); + } + + return drives; + } + + /** + * Returns a list of paths of Eclipse installations. + * Eclipse installations are found by checking for the existence of 'eclipse.exe' in the following locations: + *
      + *
    • X:\*Program Files*\*Eclipse*
    • + *
    • X:\*Eclipse*
    • + *
    + * + * Where 'X' is tried for all local disk drives, unless there's a problem calling fsutil, in which case only + * C: is tried. + */ + private static void findEclipseOnWindows(List locations, List problems) { + List driveLetters = asList("C"); + try { + driveLetters = getDrivesOnWindows(); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + + //Various try/catch/ignore statements are in this for loop. Weird conditions on the disk can cause exceptions, + //such as an unformatted drive causing a NullPointerException on listFiles. Best action is almost invariably to just + //continue onwards. + for (String letter : driveLetters) { + try { + File f = new File(letter + ":\\"); + for (File dir : f.listFiles()) { + if (!dir.isDirectory()) continue; + try { + if (dir.getName().toLowerCase().contains("eclipse")) { + String eclipseLocation = findEclipseOnWindows1(dir); + if (eclipseLocation != null) { + try { + locations.add(EclipseLocation.create(eclipseLocation)); + } catch (NotAnEclipseException e) { + problems.add(e); + } + } + } + } catch (Exception ignore) {} + + try { + if (dir.getName().toLowerCase().contains("program files")) { + for (File dir2 : dir.listFiles()) { + if (!dir2.isDirectory()) continue; + if (dir.getName().toLowerCase().contains("eclipse")) { + String eclipseLocation = findEclipseOnWindows1(dir); + if (eclipseLocation != null) { + try { + locations.add(EclipseLocation.create(eclipseLocation)); + } catch (NotAnEclipseException e) { + problems.add(e); + } + } + } + } + } + } catch (Exception ignore) {} + } + } catch (Exception ignore) {} + } + } + + /** Checks if the provided directory contains 'eclipse.exe', and if so, returns the directory, otherwise null. */ + private static String findEclipseOnWindows1(File dir) { + if (new File(dir, "eclipse.exe").isFile()) return dir.getAbsolutePath(); + return null; + } + + /** + * Calls the OS-dependent 'find Eclipse' routine. If the local OS doesn't have a routine written for it, + * null is returned. + * + * @param locations + * List of valid eclipse locations - provide an empty list; this + * method will fill it. + * @param problems + * List of eclipse locations that seem to contain half-baked + * eclipses that can't be installed. Provide an empty list; this + * method will fill it. + */ + static void findEclipses(List locations, List problems) { + switch (getOS()) { + case WINDOWS: + findEclipseOnWindows(locations, problems); + break; + case MAC_OS_X: + findEclipseOnMac(locations, problems); + break; + default: + case UNIX: + findEclipseOnUnix(locations, problems); + break; + } + } + + static enum OS { + MAC_OS_X, WINDOWS, UNIX; + } + + static OS getOS() { + String prop = System.getProperty("os.name", "").toLowerCase(); + if (prop.matches("^.*\\bmac\\b.*$")) return OS.MAC_OS_X; + if (prop.matches("^.*\\bdarwin\\b.*$")) return OS.MAC_OS_X; + if (prop.matches("^.*\\bwin(dows)\\b.*$")) return OS.WINDOWS; + + return OS.UNIX; + } + + /** + * Returns the proper name of the executable for the local OS. + * + * @return 'Eclipse.app' on OS X, 'eclipse.exe' on Windows, and 'eclipse' on other OSes. + */ + static String getEclipseExecutableName() { + switch (getOS()) { + case WINDOWS: + return "eclipse.exe"; + case MAC_OS_X: + return "Eclipse.app"; + default: + case UNIX: + return "eclipse"; + } + } + + /** Scans a couple of likely locations on linux. */ + private static void findEclipseOnUnix(List locations, List problems) { + List guesses = new ArrayList(); + + File d; + + d = new File("/usr/bin/eclipse"); + if (d.exists()) guesses.add(d.getPath()); + d = new File("/usr/local/bin/eclipse"); + if (d.exists()) guesses.add(d.getPath()); + d = new File(System.getProperty("user.home", "."), "bin/eclipse"); + if (d.exists()) guesses.add(d.getPath()); + + findEclipseInSubDir("/usr/local/share", guesses); + findEclipseInSubDir("/usr/local", guesses); + findEclipseInSubDir("/usr/share", guesses); + findEclipseInSubDir(System.getProperty("user.home", "."), guesses); + + for (String guess : guesses) { + try { + locations.add(EclipseLocation.create(guess)); + } catch (NotAnEclipseException e) { + problems.add(e); + } + } + } + + private static void findEclipseInSubDir(String dir, List guesses) { + File d = new File(dir); + if (!d.isDirectory()) return; + for (File f : d.listFiles()) { + if (f.isDirectory() && f.getName().toLowerCase().contains("eclipse")) { + File possible = new File(f, "eclipse"); + if (possible.exists()) guesses.add(possible.getAbsolutePath()); + } + } + } + + /** + * Scans /Applications for any folder named 'Eclipse' + */ + private static void findEclipseOnMac(List locations, List problems) { + for (File dir : new File("/Applications").listFiles()) { + if (!dir.isDirectory()) continue; + if (dir.getName().toLowerCase().equals("eclipse.app")) { + //This would be kind of an unorthodox Eclipse installation, but if Eclipse ever + //moves to this more maclike installation concept, our installer can still handle it. + try { + locations.add(EclipseLocation.create("/Applications")); + } catch (NotAnEclipseException e) { + problems.add(e); + } + } + if (dir.getName().toLowerCase().contains("eclipse")) { + if (new File(dir, "Eclipse.app").exists()) { + try { + locations.add(EclipseLocation.create(dir.toString())); + } catch (NotAnEclipseException e) { + problems.add(e); + } + } + } + } + } +} diff --git a/src/installer/lombok/installer/EclipseLocation.java b/src/installer/lombok/installer/EclipseLocation.java new file mode 100644 index 00000000..c43c5042 --- /dev/null +++ b/src/installer/lombok/installer/EclipseLocation.java @@ -0,0 +1,474 @@ +/* + * 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.installer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +/** + * Represents an Eclipse installation. + * An instance can figure out if an Eclipse installation has been lombok-ified, and can + * install and uninstall lombok from the Eclipse installation. + */ +final class EclipseLocation { + private static final String OS_NEWLINE; + + static { + String os = System.getProperty("os.name", ""); + + if ("Mac OS".equals(os)) OS_NEWLINE = "\r"; + else if (os.toLowerCase().contains("windows")) OS_NEWLINE = "\r\n"; + else OS_NEWLINE = "\n"; + } + + private final String name; + private final File eclipseIniPath; + private volatile boolean hasLombok; + + /** Toggling the 'selected' checkbox in the GUI is tracked via this boolean */ + boolean selected = true; + + /** + * Thrown when creating a new EclipseLocation with a path object that doesn't, in fact, + * point at an Eclipse installation. + */ + static final class NotAnEclipseException extends Exception { + private static final long serialVersionUID = 1L; + + public NotAnEclipseException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Renders a message dialog with information about what went wrong. + */ + void showDialog(JFrame appWindow) { + JOptionPane.showMessageDialog(appWindow, getMessage(), "Cannot configure Eclipse installation", JOptionPane.WARNING_MESSAGE); + } + } + + private EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws NotAnEclipseException { + this.name = nameOfLocation; + this.eclipseIniPath = pathToEclipseIni; + try { + this.hasLombok = checkForLombok(eclipseIniPath); + } catch (IOException e) { + throw new NotAnEclipseException( + "I can't read the configuration file of the Eclipse installed at " + name + "\n" + + "You may need to run this installer with root privileges if you want to modify that Eclipse.", e); + } + } + + private static final List eclipseExecutableNames = Collections.unmodifiableList(Arrays.asList( + "eclipse.app", "eclipse.exe", "eclipse")); + + /** + * Create a new EclipseLocation by pointing at either the directory contain the Eclipse executable, or the executable itself, + * or an eclipse.ini file. + * + * @throws NotAnEclipseException + * If this isn't an Eclipse executable or a directory with an + * Eclipse executable. + */ + public static EclipseLocation create(String path) throws NotAnEclipseException { + if (path == null) throw new NullPointerException("path"); + File p = new File(path); + + if (!p.exists()) throw new NotAnEclipseException("File does not exist: " + path, null); + if (p.isDirectory()) { + for (String possibleExeName : eclipseExecutableNames) { + File f = new File(p, possibleExeName); + if (f.exists()) return findEclipseIniFromExe(f, 0); + } + + File f = new File(p, "eclipse.ini"); + if (f.exists()) return new EclipseLocation(getFilePath(p), f); + } + + if (p.isFile()) { + if (p.getName().equalsIgnoreCase("eclipse.ini")) { + return new EclipseLocation(getFilePath(p.getParentFile()), p); + } + + if (eclipseExecutableNames.contains(p.getName().toLowerCase())) { + return findEclipseIniFromExe(p, 0); + } + } + + throw new NotAnEclipseException("This path does not appear to contain an Eclipse installation: " + p, null); + } + + private static EclipseLocation findEclipseIniFromExe(File exePath, int loopCounter) throws NotAnEclipseException { + /* Try looking for eclipse.ini as sibling to the executable */ { + File ini = new File(exePath.getParentFile(), "eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); + } + + /* Try looking for Eclipse/app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { + File ini = new File(exePath.getParentFile(), "Eclipse.app/Contents/MacOS/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); + } + + /* If executable is a soft link, follow it and retry. */ { + if (loopCounter < 50) { + try { + String oPath = exePath.getAbsolutePath(); + String nPath = exePath.getCanonicalPath(); + if (!oPath.equals(nPath)) try { + return findEclipseIniFromExe(new File(nPath), loopCounter + 1); + } catch (NotAnEclipseException ignore) { + // Unlinking didn't help find an eclipse, so continue. + } + } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } + } + } + + /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { + String path = exePath.getAbsolutePath(); + try { + path = exePath.getCanonicalPath(); + } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } + + if (path.equals("/usr/bin/eclipse") || path.equals("/bin/eclipse") || path.equals("/usr/local/bin/eclipse")) { + File ini = new File("/usr/lib/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/usr/local/lib/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/usr/local/etc/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/etc/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + } + } + + /* If we get this far, we lose. */ + throw new NotAnEclipseException("This path does not appear to contain an eclipse installation: " + exePath, null); + } + + public static String getFilePath(File p) { + try { + return p.getCanonicalPath(); + } catch (IOException e) { + String x = p.getAbsolutePath(); + return x == null ? p.getPath() : x; + } + } + + @Override public int hashCode() { + return eclipseIniPath.hashCode(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof EclipseLocation)) return false; + return ((EclipseLocation)o).eclipseIniPath.equals(eclipseIniPath); + } + + /** + * Returns the name of this location; generally the path to the eclipse executable. + * + * Executables: "eclipse.exe" (Windows), "Eclipse.app" (Mac OS X), "eclipse" (Linux and other unixes). + */ + String getName() { + return name; + } + + /** + * @return true if the Eclipse installation has been instrumented with lombok. + */ + boolean hasLombok() { + return hasLombok; + } + + private final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( + "^\\-javaagent\\:.*lombok.*\\.jar$", Pattern.CASE_INSENSITIVE); + + private final Pattern BOOTCLASSPATH_LINE_MATCHER = Pattern.compile( + "^\\-Xbootclasspath\\/a\\:(.*lombok.*\\.jar.*)$", Pattern.CASE_INSENSITIVE); + + private boolean checkForLombok(File iniFile) throws IOException { + if (!iniFile.exists()) return false; + FileInputStream fis = new FileInputStream(iniFile); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + if (JAVA_AGENT_LINE_MATCHER.matcher(line.trim()).matches()) return true; + } + + return false; + } finally { + fis.close(); + } + } + + /** Thrown when uninstalling lombok fails. */ + static class UninstallException extends Exception { + private static final long serialVersionUID = 1L; + + public UninstallException(String message, Throwable cause) { + super(message, cause); + } + } + + /** Returns directories that may contain lombok.jar files that need to be deleted. */ + private List getUninstallDirs() { + List result = new ArrayList(); + File x = new File(name); + if (!x.isDirectory()) x = x.getParentFile(); + if (x.isDirectory()) result.add(x); + result.add(eclipseIniPath.getParentFile()); + return result; + } + + /** + * Uninstalls lombok from this location. + * It's a no-op if lombok wasn't there in the first place, + * and it will remove a half-succeeded lombok installation as well. + * + * @throws UninstallException + * If there's an obvious I/O problem that is preventing + * installation. bugs in the uninstall code will probably throw + * other exceptions; this is intentional. + */ + void uninstall() throws UninstallException { + for (File dir : getUninstallDirs()) { + File lombokJar = new File(dir, "lombok.jar"); + if (lombokJar.exists()) { + if (!lombokJar.delete()) throw new UninstallException( + "Can't delete " + lombokJar.getAbsolutePath() + generateWriteErrorMessage(), null); + } + + /* legacy code - lombok at one point used to have a separate jar for the eclipse agent. + * Leave this code in to delete it for those upgrading from an old version. */ { + File agentJar = new File(dir, "lombok.eclipse.agent.jar"); + if (agentJar.exists()) { + if (!agentJar.delete()) throw new UninstallException( + "Can't delete " + agentJar.getAbsolutePath() + generateWriteErrorMessage(), null); + } + } + } + + StringBuilder newContents = new StringBuilder(); + if (eclipseIniPath.exists()) { + try { + FileInputStream fis = new FileInputStream(eclipseIniPath); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + if (JAVA_AGENT_LINE_MATCHER.matcher(line).matches()) continue; + Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); + if (m.matches()) { + StringBuilder elemBuilder = new StringBuilder(); + elemBuilder.append("-Xbootclasspath/a:"); + boolean first = true; + for (String elem : m.group(1).split(Pattern.quote(File.pathSeparator))) { + if (elem.toLowerCase().endsWith("lombok.jar")) continue; + /* legacy code -see previous comment that starts with 'legacy' */ { + if (elem.toLowerCase().endsWith("lombok.eclipse.agent.jar")) continue; + } + if (first) first = false; + else elemBuilder.append(File.pathSeparator); + elemBuilder.append(elem); + } + if (!first) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); + continue; + } + + newContents.append(line).append(OS_NEWLINE); + } + + } finally { + fis.close(); + } + + FileOutputStream fos = new FileOutputStream(eclipseIniPath); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + } catch (IOException e) { + throw new UninstallException("Cannot uninstall lombok from " + name + generateWriteErrorMessage(), e); + } + } + } + + /** Thrown when installing lombok fails. */ + static class InstallException extends Exception { + private static final long serialVersionUID = 1L; + + public InstallException(String message, Throwable cause) { + super(message, cause); + } + } + + private static String generateWriteErrorMessage() { + String osSpecificError; + + switch (EclipseFinder.getOS()) { + default: + case MAC_OS_X: + case UNIX: + osSpecificError = ":\nStart terminal, go to the directory with lombok.jar, and run: sudo java -jar lombok.jar"; + break; + case WINDOWS: + osSpecificError = ":\nStart a new cmd (dos box) with admin privileges, go to the directory with lombok.jar, and run: java -jar lombok.jar"; + break; + } + + return ", probably because this installer does not have the access rights.\n" + + "Try re-running the installer with administrative privileges" + osSpecificError; + } + + /** + * Install lombok into the Eclipse at this location. + * If lombok is already there, it is overwritten neatly (upgrade mode). + * + * @throws InstallException + * If there's an obvious I/O problem that is preventing + * installation. bugs in the install code will probably throw + * other exceptions; this is intentional. + */ + void install() throws InstallException { + // For whatever reason, relative paths in your eclipse.ini file don't work on linux, but only for -javaagent. + // If someone knows how to fix this, please do so, as this current hack solution (putting the absolute path + // to the jar files in your eclipse.ini) means you can't move your eclipse around on linux without lombok + // breaking it. NB: rerunning lombok.jar installer and hitting 'update' will fix it if you do that. + boolean fullPathRequired = EclipseFinder.getOS() == EclipseFinder.OS.UNIX; + + boolean installSucceeded = false; + StringBuilder newContents = new StringBuilder(); + //If 'installSucceeded' is true here, something very weird is going on, but instrumenting all of them + //is no less bad than aborting, and this situation should be rare to the point of non-existence. + + File lombokJar = new File(eclipseIniPath.getParentFile(), "lombok.jar"); + + File ourJar = EclipseFinder.findOurJar(); + byte[] b = new byte[524288]; + boolean readSucceeded = true; + try { + FileOutputStream out = new FileOutputStream(lombokJar); + try { + readSucceeded = false; + InputStream in = new FileInputStream(ourJar); + try { + while (true) { + int r = in.read(b); + if (r == -1) break; + if (r > 0) readSucceeded = true; + out.write(b, 0, r); + } + } finally { + in.close(); + } + } finally { + out.close(); + } + } catch (IOException e) { + try { + lombokJar.delete(); + } catch (Throwable ignore) { /* Nothing we can do about that. */ } + if (!readSucceeded) throw new InstallException( + "I can't read my own jar file. I think you've found a bug in this installer!\nI suggest you restart it " + + "and use the 'what do I do' link, to manually install lombok. Also, tell us about this at:\n" + + "http://groups.google.com/group/project-lombok - Thanks!", e); + throw new InstallException("I can't write to your Eclipse directory at " + name + generateWriteErrorMessage(), e); + } + + /* legacy - delete lombok.eclipse.agent.jar if its there, which lombok no longer uses. */ { + new File(lombokJar.getParentFile(), "lombok.eclipse.agent.jar").delete(); + } + + try { + FileInputStream fis = new FileInputStream(eclipseIniPath); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + if (JAVA_AGENT_LINE_MATCHER.matcher(line).matches()) continue; + Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); + if (m.matches()) { + StringBuilder elemBuilder = new StringBuilder(); + elemBuilder.append("-Xbootclasspath/a:"); + boolean first = true; + for (String elem : m.group(1).split(Pattern.quote(File.pathSeparator))) { + if (elem.toLowerCase().endsWith("lombok.jar")) continue; + /* legacy code -see previous comment that starts with 'legacy' */ { + if (elem.toLowerCase().endsWith("lombok.eclipse.agent.jar")) continue; + } + if (first) first = false; + else elemBuilder.append(File.pathSeparator); + elemBuilder.append(elem); + } + if (!first) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); + continue; + } + + newContents.append(line).append(OS_NEWLINE); + } + + } finally { + fis.close(); + } + + String fullPathToLombok = fullPathRequired ? (lombokJar.getParentFile().getCanonicalPath() + File.separator) : ""; + + newContents.append(String.format( + "-javaagent:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); + newContents.append(String.format( + "-Xbootclasspath/a:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); + + FileOutputStream fos = new FileOutputStream(eclipseIniPath); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + installSucceeded = true; + } catch (IOException e) { + throw new InstallException("Cannot install lombok at " + name + generateWriteErrorMessage(), e); + } finally { + if (!installSucceeded) try { + lombokJar.delete(); + } catch (Throwable ignore) {} + } + + if (!installSucceeded) { + throw new InstallException("I can't find the eclipse.ini file. Is this a real Eclipse installation?", null); + } + } +} diff --git a/src/installer/lombok/installer/Installer.java b/src/installer/lombok/installer/Installer.java new file mode 100644 index 00000000..e1da5d31 --- /dev/null +++ b/src/installer/lombok/installer/Installer.java @@ -0,0 +1,895 @@ +/* + * 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.installer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.HeadlessException; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.font.TextAttribute; +import java.io.File; +import java.io.FilenameFilter; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.Scrollable; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.filechooser.FileFilter; + +import lombok.core.Version; +import lombok.installer.EclipseFinder.OS; +import lombok.installer.EclipseLocation.InstallException; +import lombok.installer.EclipseLocation.NotAnEclipseException; +import lombok.installer.EclipseLocation.UninstallException; + +/** + * The lombok installer proper. + * Uses swing to show a simple GUI that can add and remove the java agent to Eclipse installations. + * Also offers info on what this installer does in case people want to instrument their Eclipse manually, + * and looks in some common places on Mac OS X and Windows. + */ +public class Installer { + private static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); + + private JFrame appWindow; + + private JComponent loadingExpl; + + private Component javacArea; + private Component eclipseArea; + private Component uninstallArea; + private Component howIWorkArea; + + private Box uninstallBox; + private List toUninstall; + + private JHyperLink uninstallButton; + private JLabel uninstallPlaceholder; + private JButton installButton; + + public static void main(String[] args) { + if (args.length > 0 && (args[0].equals("install") || args[0].equals("uninstall"))) { + boolean uninstall = args[0].equals("uninstall"); + if (args.length < 3 || !args[1].equals("eclipse")) { + System.err.printf("Run java -jar lombok.jar %1$s eclipse path/to/eclipse/executable (or 'auto' to %1$s to all auto-discovered eclipse locations)\n", uninstall ? "uninstall" : "install"); + System.exit(1); + } + String path = args[2]; + try { + final List locations = new ArrayList(); + final List problems = new ArrayList(); + if (path.equals("auto")) { + EclipseFinder.findEclipses(locations, problems); + } else { + locations.add(EclipseLocation.create(path)); + } + int validLocations = locations.size(); + for (EclipseLocation loc : locations) { + try { + if (uninstall) { + loc.uninstall(); + } else { + loc.install(); + } + System.out.printf("Lombok %s %s: %s\n", uninstall ? "uninstalled" : "installed", uninstall ? "from" : "to", loc.getName()); + } catch (InstallException e) { + System.err.printf("Installation at %s failed:\n", loc.getName()); + System.err.println(e.getMessage()); + validLocations--; + } catch (UninstallException e) { + System.err.printf("Uninstall at %s failed:\n", loc.getName()); + System.err.println(e.getMessage()); + validLocations--; + } + } + for (NotAnEclipseException problem : problems) { + System.err.println("WARNING: " + problem.getMessage()); + } + if (validLocations == 0) { + System.err.println("WARNING: Zero valid locations found; so nothing was done."); + } + System.exit(0); + } catch (NotAnEclipseException e) { + System.err.println("Not a valid eclipse location:"); + System.err.println(e.getMessage()); + System.exit(2); + } + } + + if (args.length > 0 && args[0].equals("uninstall")) { + if (args.length < 3 || !args[1].equals("eclipse")) { + System.err.println("Run java -jar lombok.jar uninstall eclipse path/to/eclipse/executable (or 'auto' to uninstall all auto-discovered eclipse locations)"); + System.exit(1); + } + String path = args[2]; + try { + EclipseLocation loc = EclipseLocation.create(path); + loc.uninstall(); + System.out.println("Uninstalled from: " + loc.getName()); + System.exit(0); + } catch (NotAnEclipseException e) { + System.err.println("Not a valid eclipse location:"); + System.err.println(e.getMessage()); + System.exit(2); + } catch (UninstallException e) { + System.err.println("Uninstall failed:"); + System.err.println(e.getMessage()); + System.exit(1); + } + } + + if (EclipseFinder.getOS() == OS.MAC_OS_X) { + System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Lombok Installer"); + System.setProperty("com.apple.macos.use-file-dialog-packages", "true"); + } + + try { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ignore) {} + + new Installer().show(); + } catch (HeadlessException e) { + printHeadlessInfo(); + } + } + }); + } catch (HeadlessException e) { + printHeadlessInfo(); + } + } + + /** + * If run in headless mode, the installer can't show its fancy GUI. There's little point in running + * the installer without a GUI environment, as Eclipse doesn't run in headless mode either, so + * we'll make do with showing some basic info on Lombok as well as instructions for using lombok with javac. + */ + private static void printHeadlessInfo() { + System.out.printf("About lombok v%s\n" + + "Lombok makes java better by providing very spicy additions to the Java programming language," + + "such as using @Getter to automatically generate a getter method for any field.\n\n" + + "Browse to %s for more information. To install lombok on Eclipse, re-run this jar file on a " + + "graphical computer system - this message is being shown because your terminal is not graphics capable." + + "If you are just using 'javac' or a tool that calls on javac, no installation is neccessary; just " + + "make sure lombok.jar is in the classpath when you compile. Example:\n\n" + + " java -cp lombok.jar MyCode.java\n\n\n" + + "If for whatever reason you can't run the graphical installer but you do want to install lombok into eclipse," + + "start this jar with the following syntax:\n\n" + + " java -jar lombok.jar install eclipse path/to/your/eclipse/executable", Version.getVersion(), ABOUT_LOMBOK_URL); + } + + /** + * Creates a new installer that starts out invisible. + * Call the {@link #show()} method on a freshly created installer to render it. + */ + public Installer() { + appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); + + appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + appWindow.setResizable(false); + appWindow.setIconImage(Toolkit.getDefaultToolkit().getImage(Installer.class.getResource("lombokIcon.png"))); + + try { + javacArea = buildJavacArea(); + eclipseArea = buildEclipseArea(); + uninstallArea = buildUninstallArea(); + uninstallArea.setVisible(false); + howIWorkArea = buildHowIWorkArea(); + howIWorkArea.setVisible(false); + buildChrome(appWindow.getContentPane()); + appWindow.pack(); + } catch (Throwable t) { + handleException(t); + } + } + + private void handleException(final Throwable t) { + SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, "There was a problem during the installation process:\n" + t, "Uh Oh!", JOptionPane.ERROR_MESSAGE); + t.printStackTrace(); + System.exit(1); + } + }); + } + + private Component buildHowIWorkArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + container.add(new JLabel(HOW_I_WORK_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(String.format(HOW_I_WORK_EXPLANATION, File.pathSeparator)), constraints); + + Box buttonBar = Box.createHorizontalBox(); + JButton backButton = new JButton("Okay - Good to know!"); + buttonBar.add(Box.createHorizontalGlue()); + buttonBar.add(backButton); + + backButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + howIWorkArea.setVisible(false); + javacArea.setVisible(true); + eclipseArea.setVisible(true); + appWindow.pack(); + } + }); + + constraints.gridy = 2; + container.add(buttonBar, constraints); + + return container; + } + + private Component buildUninstallArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + container.add(new JLabel(UNINSTALL_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(UNINSTALL_EXPLANATION), constraints); + + uninstallBox = Box.createVerticalBox(); + constraints.gridy = 2; + constraints.fill = GridBagConstraints.HORIZONTAL; + container.add(uninstallBox, constraints); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 3; + container.add(new JLabel("Are you sure?"), constraints); + + Box buttonBar = Box.createHorizontalBox(); + JButton noButton = new JButton("No - Don't uninstall"); + buttonBar.add(noButton); + buttonBar.add(Box.createHorizontalGlue()); + JButton yesButton = new JButton("Yes - uninstall Lombok"); + buttonBar.add(yesButton); + + noButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + uninstallArea.setVisible(false); + javacArea.setVisible(true); + eclipseArea.setVisible(true); + appWindow.pack(); + } + }); + + yesButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + doUninstall(); + } + }); + + constraints.gridy = 4; + container.add(buttonBar, constraints); + + return container; + } + + private Component buildJavacArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + constraints.insets = new Insets(8, 0, 0, 16); + + container.add(new JLabel(JAVAC_TITLE), constraints); + + constraints.gridy = 1; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + container.add(new JLabel(JAVAC_EXPLANATION), constraints); + + JLabel example = new JLabel(JAVAC_EXAMPLE); + + constraints.gridy = 2; + container.add(example, constraints); + return container; + } + + private Component buildEclipseArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(ECLIPSE_TITLE), constraints); + + constraints.gridy = 1; + container.add(new JLabel(ECLIPSE_EXPLANATION), constraints); + + constraints.gridy = 2; + loadingExpl = Box.createHorizontalBox(); + loadingExpl.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); + loadingExpl.add(new JLabel(ECLIPSE_LOADING_EXPLANATION)); + container.add(loadingExpl, constraints); + + constraints.weightx = 1.0; + constraints.gridy = 3; + constraints.fill = GridBagConstraints.HORIZONTAL; + eclipsesList = new EclipsesList(); + + JScrollPane eclipsesListScroll = new JScrollPane(eclipsesList); + eclipsesListScroll.setBackground(Color.WHITE); + eclipsesListScroll.getViewport().setBackground(Color.WHITE); + container.add(eclipsesListScroll, constraints); + + Thread findEclipsesThread = new Thread() { + @Override public void run() { + try { + final List locations = new ArrayList(); + final List problems = new ArrayList(); + EclipseFinder.findEclipses(locations, problems); + + SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + for (EclipseLocation location : locations) { + try { + eclipsesList.addEclipse(location); + } catch (Throwable t) { + handleException(t); + } + } + + for (NotAnEclipseException problem : problems) { + problem.showDialog(appWindow); + } + + loadingExpl.setVisible(false); + + if (locations.size() + problems.size() == 0) { + JOptionPane.showMessageDialog(appWindow, + "I don't know how to automatically find Eclipse installations on this platform.\n" + + "Please use the 'Specify Eclipse Location...' button to manually point out the\n" + + "location of your Eclipse installation to me. Thanks!", "Can't find Eclipse", JOptionPane.INFORMATION_MESSAGE); + } + } + }); + } catch (Throwable t) { + handleException(t); + } + } + }; + + findEclipsesThread.start(); + + Box buttonBar = Box.createHorizontalBox(); + JButton specifyEclipseLocationButton = new JButton("Specify Eclipse location..."); + buttonBar.add(specifyEclipseLocationButton); + specifyEclipseLocationButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent event) { + final String exeName = EclipseFinder.getEclipseExecutableName(); + String file = null; + + if (EclipseFinder.getOS() == OS.MAC_OS_X) { + FileDialog chooser = new FileDialog(appWindow); + chooser.setMode(FileDialog.LOAD); + chooser.setFilenameFilter(new FilenameFilter() { + @Override public boolean accept(File dir, String fileName) { + if (exeName.equalsIgnoreCase(fileName)) return true; + if (new File(dir, fileName).isDirectory()) return true; + return false; + } + }); + + chooser.setVisible(true); + file = new File(chooser.getDirectory(), chooser.getFile()).getAbsolutePath(); + } else { + JFileChooser chooser = new JFileChooser(); + + chooser.setAcceptAllFileFilterUsed(false); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + chooser.setFileFilter(new FileFilter() { + @Override public boolean accept(File f) { + if (f.getName().equalsIgnoreCase(exeName)) return true; + if (f.getName().equalsIgnoreCase("eclipse.ini")) return true; + if (f.isDirectory()) return true; + + return false; + } + + @Override public String getDescription() { + return "Eclipse Installation"; + } + }); + + switch (chooser.showDialog(appWindow, "Select")) { + case JFileChooser.APPROVE_OPTION: + file = chooser.getSelectedFile().getAbsolutePath(); + } + } + + if (file != null) { + try { + eclipsesList.addEclipse(EclipseLocation.create(file)); + } catch (NotAnEclipseException e) { + e.showDialog(appWindow); + } catch (Throwable t) { + handleException(t); + } + } + } + }); + + buttonBar.add(Box.createHorizontalGlue()); + installButton = new JButton("Install / Update"); + buttonBar.add(installButton); + + installButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + List locationsToInstall = new ArrayList(eclipsesList.getSelectedEclipses()); + if (locationsToInstall.isEmpty()) { + JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations!.", "No Selection", JOptionPane.WARNING_MESSAGE); + return; + } + + install(locationsToInstall); + } + }); + + constraints.gridy = 4; + constraints.weightx = 0; + container.add(buttonBar, constraints); + + constraints.gridy = 5; + constraints.fill = GridBagConstraints.NONE; + JHyperLink showMe = new JHyperLink("Show me what this installer will do to my Eclipse installation."); + container.add(showMe, constraints); + showMe.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + showWhatIDo(); + } + }); + + constraints.gridy = 6; + uninstallButton = new JHyperLink("Uninstall lombok from selected Eclipse installations."); + uninstallPlaceholder = new JLabel(" "); + uninstallButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + List locationsToUninstall = new ArrayList(); + for (EclipseLocation location : eclipsesList.getSelectedEclipses()) { + if (location.hasLombok()) locationsToUninstall.add(location); + } + + if (locationsToUninstall.isEmpty()) { + JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations that have been lombok-enabled.", "No Selection", JOptionPane.WARNING_MESSAGE); + return; + } + + + uninstall(locationsToUninstall); + } + }); + container.add(uninstallButton, constraints); + uninstallPlaceholder.setVisible(false); + container.add(uninstallPlaceholder, constraints); + + + return container; + } + + private void showWhatIDo() { + javacArea.setVisible(false); + eclipseArea.setVisible(false); + howIWorkArea.setVisible(true); + appWindow.pack(); + } + + private void uninstall(List locations) { + javacArea.setVisible(false); + eclipseArea.setVisible(false); + + uninstallBox.removeAll(); + uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); + for (EclipseLocation location : locations) { + JLabel label = new JLabel(location.getName()); + label.setFont(label.getFont().deriveFont(Font.BOLD)); + uninstallBox.add(label); + } + uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); + + toUninstall = locations; + uninstallArea.setVisible(true); + appWindow.pack(); + } + + private void install(final List toInstall) { + JPanel spinner = new JPanel(); + spinner.setOpaque(true); + spinner.setLayout(new FlowLayout()); + spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); + appWindow.setContentPane(spinner); + + final AtomicReference success = new AtomicReference(true); + + new Thread() { + @Override public void run() { + for (EclipseLocation loc : toInstall) { + try { + loc.install(); + } catch (final InstallException e) { + success.set(false); + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, + e.getMessage(), "Install Problem", JOptionPane.ERROR_MESSAGE); + } + }); + } catch (Exception e2) { + //Shouldn't happen. + throw new RuntimeException(e2); + } + } + } + + if (success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, + "Lombok has been installed on the selected Eclipse installations.
    " + + "Don't forget to add lombok.jar to your projects, and restart your eclipse!
    " + + "If you start eclipse with a custom -vm parameter, you'll need to add:
    " + + "-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar
    " + + "as parameter as well.", "Install successful", + JOptionPane.INFORMATION_MESSAGE); + appWindow.setVisible(false); + System.exit(0); + } + }); + + if (!success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + System.exit(0); + } + }); + } + }.start(); + } + + private void doUninstall() { + JPanel spinner = new JPanel(); + spinner.setOpaque(true); + spinner.setLayout(new FlowLayout()); + spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); + + appWindow.setContentPane(spinner); + + final AtomicReference success = new AtomicReference(true); + new Thread() { + @Override public void run() { + for (EclipseLocation loc : toUninstall) { + try { + loc.uninstall(); + } catch (final UninstallException e) { + success.set(false); + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, + e.getMessage(), "Uninstall Problem", JOptionPane.ERROR_MESSAGE); + } + }); + } catch (Exception e2) { + //Shouldn't happen. + throw new RuntimeException(e2); + } + } + } + + if (success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, "Lombok has been removed from the selected Eclipse installations.", "Uninstall successful", JOptionPane.INFORMATION_MESSAGE); + appWindow.setVisible(false); + System.exit(0); + } + }); + } + }.start(); + } + + private EclipsesList eclipsesList = new EclipsesList(); + + private static class JHyperLink extends JButton { + private static final long serialVersionUID = 1L; + + public JHyperLink(String text) { + super(); + setFont(getFont().deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, 1))); + setText(text); + setBorder(null); + setContentAreaFilled(false); + setForeground(Color.BLUE); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + setMargin(new Insets(0, 0, 0, 0)); + } + } + + void selectedLomboksChanged(List selectedEclipses) { + boolean uninstallAvailable = false; + boolean installAvailable = false; + for (EclipseLocation loc : selectedEclipses) { + if (loc.hasLombok()) uninstallAvailable = true; + installAvailable = true; + } + + uninstallButton.setVisible(uninstallAvailable); + uninstallPlaceholder.setVisible(!uninstallAvailable); + installButton.setEnabled(installAvailable); + } + + private class EclipsesList extends JPanel implements Scrollable { + private static final long serialVersionUID = 1L; + + List locations = new ArrayList(); + + EclipsesList() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBackground(Color.WHITE); + } + + List getSelectedEclipses() { + List list = new ArrayList(); + for (EclipseLocation loc : locations) if (loc.selected) list.add(loc); + return list; + } + + void fireSelectionChange() { + selectedLomboksChanged(getSelectedEclipses()); + } + + void addEclipse(final EclipseLocation location) { + if (locations.contains(location)) return; + Box box = Box.createHorizontalBox(); + box.setBackground(Color.WHITE); + final JCheckBox checkbox = new JCheckBox(location.getName()); + checkbox.setBackground(Color.WHITE); + box.add(checkbox); + checkbox.setSelected(true); + checkbox.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + location.selected = checkbox.isSelected(); + fireSelectionChange(); + } + }); + + if (location.hasLombok()) { + box.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombokIcon.png")))); + } + box.add(Box.createHorizontalGlue()); + locations.add(location); + add(box); + getParent().doLayout(); + fireSelectionChange(); + } + + @Override public Dimension getPreferredScrollableViewportSize() { + return new Dimension(1, 100); + } + + @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return 12; + } + + @Override public boolean getScrollableTracksViewportHeight() { + return false; + } + + @Override public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 1; + } + } + + private void buildChrome(Container appWindowContainer) { + JLabel leftGraphic = new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombok.png"))); + + GridBagConstraints constraints = new GridBagConstraints(); + + appWindowContainer.setLayout(new GridBagLayout()); + + constraints.gridheight = 3; + constraints.gridwidth = 1; + constraints.gridx = 0; + constraints.gridy = 0; + constraints.insets = new Insets(8, 8, 8, 8); + appWindowContainer.add(leftGraphic, constraints); + constraints.insets = new Insets(0, 0, 0, 0); + + constraints.gridx++; + constraints.gridy++; + constraints.gridheight = 1; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.ipadx = 16; + constraints.ipady = 14; + appWindowContainer.add(javacArea, constraints); + + constraints.gridy++; + appWindowContainer.add(eclipseArea, constraints); + + appWindowContainer.add(uninstallArea, constraints); + + appWindowContainer.add(howIWorkArea, constraints); + + constraints.gridy++; + constraints.gridwidth = 2; + constraints.gridx = 0; + constraints.weightx = 0; + constraints.weighty = 0; + constraints.ipadx = 0; + constraints.ipady = 0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.SOUTHEAST; + constraints.insets = new Insets(0, 16, 8, 8); + Box buttonBar = Box.createHorizontalBox(); + JButton quitButton = new JButton("Quit Installer"); + quitButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + appWindow.setVisible(false); + System.exit(0); + } + }); + final JHyperLink hyperlink = new JHyperLink(ABOUT_LOMBOK_URL.toString()); + hyperlink.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent event) { + hyperlink.setForeground(new Color(85, 145, 90)); + try { + //java.awt.Desktop doesn't exist in 1.5. + Object desktop = Class.forName("java.awt.Desktop").getMethod("getDesktop").invoke(null); + Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, ABOUT_LOMBOK_URL); + } catch (Exception e) { + Runtime rt = Runtime.getRuntime(); + try { + switch (EclipseFinder.getOS()) { + case WINDOWS: + String[] cmd = new String[4]; + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + cmd[2] = "start"; + cmd[3] = ABOUT_LOMBOK_URL.toString(); + rt.exec(cmd); + break; + case MAC_OS_X: + rt.exec("open " + ABOUT_LOMBOK_URL.toString()); + break; + default: + case UNIX: + rt.exec("firefox " + ABOUT_LOMBOK_URL.toString()); + break; + } + } catch (Exception e2) { + JOptionPane.showMessageDialog(appWindow, + "Well, this is embarrassing. I don't know how to open a webbrowser.\n" + + "I guess you'll have to open it. Browse to:\n" + + "http://projectlombok.org for more information about Lombok.", + "I'm embarrassed", JOptionPane.INFORMATION_MESSAGE); + } + } + } + }); + buttonBar.add(hyperlink); + buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); + buttonBar.add(new JLabel("v" + Version.getVersion() + "")); + + buttonBar.add(Box.createHorizontalGlue()); + buttonBar.add(quitButton); + appWindow.add(buttonBar, constraints); + } + + /** + * Makes the installer window visible. + */ + public void show() { + appWindow.setVisible(true); + if (EclipseFinder.getOS() == OS.MAC_OS_X) { + try { + AppleNativeLook.go(); + } catch (Throwable ignore) { + //We're just prettying up the app. If it fails, meh. + } + } + } + + private static final String ECLIPSE_TITLE = + "Eclipse"; + + private static final String ECLIPSE_EXPLANATION = + "Lombok can update your Eclipse to fully support all Lombok features.
    " + + "Select Eclipse installations below and hit 'Install/Update'."; + + private static final String ECLIPSE_LOADING_EXPLANATION = + "Scanning your drives for Eclipse installations..."; + + private static final String JAVAC_TITLE = + "Javac       (and tools that invoke javac such as ant and maven)"; + + private static final String JAVAC_EXPLANATION = + "Lombok works 'out of the box' with javac.
    Just make sure the lombok.jar is in your classpath when you compile."; + + private static final String JAVAC_EXAMPLE = + "Example: javac -cp lombok.jar MyCode.java"; + + private static final String UNINSTALL_TITLE = + "Uninstall"; + + private static final String UNINSTALL_EXPLANATION = + "Uninstall Lombok from the following Eclipse Installations?"; + + private static final String HOW_I_WORK_TITLE = + "What this installer does"; + + private static final String HOW_I_WORK_EXPLANATION = + "
      " + + "
    1. First, I copy myself (lombok.jar) to your Eclipse install directory.
    2. " + + "
    3. Then, I edit the eclipse.ini file to add the following two entries:
      " + + "
      -Xbootclasspath/a:lombok.jar
      " + + "-javaagent:lombok.jar
    " + + "
    " + + "That's all there is to it. Note that on Mac OS X, eclipse.ini is hidden in
    " + + "Eclipse.app%1$sContents%1$sMacOS so that's where I place the jar files."; +} diff --git a/src/installer/lombok/installer/WindowsDriveInfo-i386.dll b/src/installer/lombok/installer/WindowsDriveInfo-i386.dll new file mode 100644 index 00000000..eb7fa49a Binary files /dev/null and b/src/installer/lombok/installer/WindowsDriveInfo-i386.dll differ diff --git a/src/installer/lombok/installer/WindowsDriveInfo-x86_64.dll b/src/installer/lombok/installer/WindowsDriveInfo-x86_64.dll new file mode 100644 index 00000000..0b7c9a83 Binary files /dev/null and b/src/installer/lombok/installer/WindowsDriveInfo-x86_64.dll differ diff --git a/src/installer/lombok/installer/WindowsDriveInfo.java b/src/installer/lombok/installer/WindowsDriveInfo.java new file mode 100644 index 00000000..41a6b17e --- /dev/null +++ b/src/installer/lombok/installer/WindowsDriveInfo.java @@ -0,0 +1,127 @@ +/* + * 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.installer; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class uses native calls on windows to figure out all drives, + * and, for each drive, if its a harddisk or something else. + * + * The output is essentially equivalent to running windows executable: + *
    fsutil fsinfo drives
    + * and + *
    fsutil fsinfo drivetype C:
    + * + * except that (A) fsutil requires privileges, (B) someone might have moved + * it out of the path or some such, and (C) its output is internationalized, + * so unless you want to include a table of how to say "Fixed Disk" in 300 + * languages, this really is a superior solution. + *

    + * To compile it, you'll need windows, as well as MinGW: + * http://sourceforge.net/projects/mingw/files/ + *

    + * Fetch gcc 4.0.4+, you don't need anything extra. Toss /c/mingw/bin in + * your git bash prompt's path (/etc/profile) and then run: + * + * $ gcc -c \ + -I "/c/Program Files/Java/jdk1.6.0_14/include" \ + -I "/c/Program Files/Java/jdk1.6.0_14/include/win32" \ + -D__int64="long long" lombok_installer_WindowsDriveInfo.c + * + * $ dllwrap.exe --add-stdcall-alias \ + -o WindowsDriveInfo-i386.dll \ + lombok_installer_WindowsDriveInfo.o + * + * You may get a warning along the lines of "Creating an export definition". + * This is expected behaviour. + * + *

    + * Now download MinGW-w64 to build the 64-bit version of the dll (you thought you were done, weren't you?) + * from: http://sourceforge.net/projects/mingw-w64/files/ + * (under toolchains targetting Win64 / Release for GCC 4.4.0 (or later) / the version for your OS.) + * + * Then, do this all over again, but this time with the x86_64-w64-mingw32-gcc and + * x86_64-w64-mingw32-dllwrap versions that are part of the MinGW-w64 distribution. + * Name the dll 'WindowsDriveInfo-x86_64.dll'. + * + * Both the 32-bit and 64-bit DLLs that this produces have been checked into the git repository + * under src/lombok/installer so you won't need to build them again unless you make some changes to + * the code in the winsrc directory. + */ +public class WindowsDriveInfo { + /** + * Return a list of all available drive letters, such as ["A", "C", "D"]. + */ + public List getLogicalDrives() { + int flags = getLogicalDrives0(); + + List letters = new ArrayList(); + for (int i = 0; i < 26; i++) { + if ((flags & (1 << i)) != 0) letters.add(Character.toString((char)('A' + i))); + } + + return letters; + } + + /** + * Calls kernel32's GetLogicalDrives, which returns an int containing + * flags; bit 0 corresponds to drive A, bit 25 to drive Z. on = disk exists. + */ + private native int getLogicalDrives0(); + + /** + * Feed it a drive letter (such as 'A') to see if it is a fixed disk. + */ + public boolean isFixedDisk(String letter) { + if (letter.length() != 1) throw new IllegalArgumentException("Supply 1 letter, not: " + letter); + char drive = Character.toUpperCase(letter.charAt(0)); + if (drive < 'A' || drive > 'Z') throw new IllegalArgumentException( + "A drive is indicated by a letter, so A-Z inclusive. Not " + drive); + return getDriveType(drive + ":\\") == 3L; + } + + /** + * Mirror of kernel32's GetDriveTypeA. You must pass in 'A:\\' - + * so including both a colon and a backslash! + * + * 0 = error + * 1 = doesn't exist + * 2 = removable drive + * 3 = fixed disk + * 4 = remote (network) disk + * 5 = cd-rom + * 6 = ram disk + */ + private native int getDriveType(String name); + + public static void main(String[] args) { + System.loadLibrary("WindowsDriveInfo"); + WindowsDriveInfo info = new WindowsDriveInfo(); + + for (String letter : info.getLogicalDrives()) { + System.out.printf("Drive %s: - %s\n", letter, + info.isFixedDisk(letter) ? "Fixed Disk" : "Not Fixed Disk"); + } + } +} diff --git a/src/installer/lombok/installer/loading.gif b/src/installer/lombok/installer/loading.gif new file mode 100644 index 00000000..b9fc304a Binary files /dev/null and b/src/installer/lombok/installer/loading.gif differ diff --git a/src/installer/lombok/installer/lombok.png b/src/installer/lombok/installer/lombok.png new file mode 100644 index 00000000..d4efde04 Binary files /dev/null and b/src/installer/lombok/installer/lombok.png differ diff --git a/src/installer/lombok/installer/lombok.svg b/src/installer/lombok/installer/lombok.svg new file mode 100644 index 00000000..0d561aea --- /dev/null +++ b/src/installer/lombok/installer/lombok.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/installer/lombok/installer/lombokIcon.png b/src/installer/lombok/installer/lombokIcon.png new file mode 100644 index 00000000..48fd4307 Binary files /dev/null and b/src/installer/lombok/installer/lombokIcon.png differ diff --git a/src/installer/lombok/installer/lombokText.png b/src/installer/lombok/installer/lombokText.png new file mode 100644 index 00000000..279746cb Binary files /dev/null and b/src/installer/lombok/installer/lombokText.png differ diff --git a/src/installer/lombok/installer/lombokText.svg b/src/installer/lombok/installer/lombokText.svg new file mode 100644 index 00000000..9fd2f73b --- /dev/null +++ b/src/installer/lombok/installer/lombokText.svg @@ -0,0 +1,67 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/installer/lombok/installer/package-info.java b/src/installer/lombok/installer/package-info.java new file mode 100644 index 00000000..14b329b4 --- /dev/null +++ b/src/installer/lombok/installer/package-info.java @@ -0,0 +1,28 @@ +/* + * 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 the lombok installer. It explains to any user that double-clicks the lombok.jar what + * lombok is about, and has the ability to instrument (or remove existing Lombok instrumentation) from any + * Eclipse installation. This package also contains the graphics uses in the installer in SVG format. + */ +package lombok.installer; diff --git a/src/lombok/AccessLevel.java b/src/lombok/AccessLevel.java deleted file mode 100644 index 16edd108..00000000 --- a/src/lombok/AccessLevel.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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/lombok/Cleanup.java b/src/lombok/Cleanup.java deleted file mode 100644 index ce9e0aa9..00000000 --- a/src/lombok/Cleanup.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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/lombok/Data.java b/src/lombok/Data.java deleted file mode 100644 index 488de640..00000000 --- a/src/lombok/Data.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/lombok/EqualsAndHashCode.java b/src/lombok/EqualsAndHashCode.java deleted file mode 100644 index 88d72051..00000000 --- a/src/lombok/EqualsAndHashCode.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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/lombok/Getter.java b/src/lombok/Getter.java deleted file mode 100644 index fa84954c..00000000 --- a/src/lombok/Getter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/lombok/Lombok.java b/src/lombok/Lombok.java deleted file mode 100644 index 71684f4f..00000000 --- a/src/lombok/Lombok.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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/lombok/NonNull.java b/src/lombok/NonNull.java deleted file mode 100644 index 08eec2a5..00000000 --- a/src/lombok/NonNull.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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/lombok/Setter.java b/src/lombok/Setter.java deleted file mode 100644 index 778bb00d..00000000 --- a/src/lombok/Setter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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/lombok/SneakyThrows.java b/src/lombok/SneakyThrows.java deleted file mode 100644 index 1feeadf1..00000000 --- a/src/lombok/SneakyThrows.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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/lombok/Synchronized.java b/src/lombok/Synchronized.java deleted file mode 100644 index 72c44c71..00000000 --- a/src/lombok/Synchronized.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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/lombok/ToString.java b/src/lombok/ToString.java deleted file mode 100644 index 7b89d481..00000000 --- a/src/lombok/ToString.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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/lombok/core/AST.java b/src/lombok/core/AST.java deleted file mode 100644 index 6d786d1e..00000000 --- a/src/lombok/core/AST.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * 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/lombok/core/AnnotationValues.java b/src/lombok/core/AnnotationValues.java deleted file mode 100644 index 0408de85..00000000 --- a/src/lombok/core/AnnotationValues.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * 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 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 getRawExpressions(String annotationMethodName) { - AnnotationValue v = values.get(annotationMethodName); - return v == null ? Collections.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 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 getProbableFQTypes(String annotationMethodName) { - List result = new ArrayList(); - 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 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/lombok/core/LombokNode.java b/src/lombok/core/LombokNode.java deleted file mode 100644 index c8ee4c00..00000000 --- a/src/lombok/core/LombokNode.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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, L extends LombokNode, N> { - protected final A ast; - protected final Kind kind; - protected final N node; - protected final List 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 children, Kind kind) { - this.ast = ast; - this.kind = kind; - this.node = node; - this.children = children == null ? new ArrayList() : 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 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 down() { - return new ArrayList(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 all children. - */ - public void rebuild() { - Map oldNodes = new IdentityHashMap(); - gatherAndRemoveChildren(oldNodes); - - L newNode = ast.buildTree(get(), kind); - - ast.replaceNewWithExistingOld(oldNodes, newNode); - } - - @SuppressWarnings("unchecked") - private void gatherAndRemoveChildren(Map 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/lombok/core/PrintAST.java b/src/lombok/core/PrintAST.java deleted file mode 100644 index df1b652c..00000000 --- a/src/lombok/core/PrintAST.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/lombok/core/SpiLoadUtil.java b/src/lombok/core/SpiLoadUtil.java deleted file mode 100644 index 0a97af7e..00000000 --- a/src/lombok/core/SpiLoadUtil.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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 Iterable findServices(Class 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 Iterable findServices(final Class target, final ClassLoader loader) throws IOException { - Enumeration resources = loader.getResources("META-INF/services/" + target.getName()); - final Set entries = new LinkedHashSet(); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - readServicesFromUrl(entries, url); - } - - final Iterator names = entries.iterator(); - return new Iterable () { - @Override public Iterator iterator() { - return new Iterator() { - @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 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}. - * - * 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 findAnnotationClass(Class c, Class base) { - if (c == Object.class || c == null) return null; - for (Type iface : c.getGenericInterfaces()) { - if (iface instanceof ParameterizedType) { - ParameterizedType p = (ParameterizedType)iface; - if (!base.equals(p.getRawType())) continue; - Type target = p.getActualTypeArguments()[0]; - if (target instanceof Class) { - if (Annotation.class.isAssignableFrom((Class) target)) { - return (Class) target; - } - } - - throw new ClassCastException("Not an annotation type: " + target); - } - } - - Class potential = findAnnotationClass(c.getSuperclass(), base); - if (potential != null) return potential; - for (Class iface : c.getInterfaces()) { - potential = findAnnotationClass(iface, base); - if (potential != null) return potential; - } - - return null; - } -} diff --git a/src/lombok/core/TransformationsUtil.java b/src/lombok/core/TransformationsUtil.java deleted file mode 100644 index 6b457927..00000000 --- a/src/lombok/core/TransformationsUtil.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 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 - * not 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 toAllGetterNames(CharSequence fieldName, boolean isBoolean) { - if (!isBoolean) return Collections.singletonList(toGetterName(fieldName, false)); - - List baseNames = new ArrayList(); - 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 names = new HashSet(); - 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(names); - } -} diff --git a/src/lombok/core/TypeLibrary.java b/src/lombok/core/TypeLibrary.java deleted file mode 100644 index 5de01b70..00000000 --- a/src/lombok/core/TypeLibrary.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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> simpleToQualifiedMap = new HashMap>(); - - /** - * 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 existing = simpleToQualifiedMap.get(keyName); - Set set = (existing == null) ? new HashSet() : new HashSet(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 findCompatible(String typeReference) { - Set result = simpleToQualifiedMap.get(typeReference); - return result == null ? Collections.emptySet() : result; - } -} diff --git a/src/lombok/core/TypeResolver.java b/src/lombok/core/TypeResolver.java deleted file mode 100644 index dd1d9a53..00000000 --- a/src/lombok/core/TypeResolver.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 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 importStrings) { - this.library = library; - this.imports = makeImportList(packageString, importStrings); - } - - private static Collection makeImportList(String packageString, Collection importStrings) { - Set imports = new HashSet(); - if (packageString != null) imports.add(packageString + ".*"); - imports.addAll(importStrings == null ? Collections.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 findTypeMatches(LombokNode context, String typeRef) { - Collection 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 eliminateImpossibleMatches(Collection potentialMatches) { - Set results = new HashSet(); - - for (String importedType : imports) { - Collection reduced = new HashSet(library.findCompatible(importedType)); - reduced.retainAll(potentialMatches); - results.addAll(reduced); - } - - return results; - } - - private boolean nameConflictInImportList(String simpleName, Collection 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/lombok/core/Version.java b/src/lombok/core/Version.java deleted file mode 100644 index 37944218..00000000 --- a/src/lombok/core/Version.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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/lombok/core/package-info.java b/src/lombok/core/package-info.java deleted file mode 100644 index 0dc5225c..00000000 --- a/src/lombok/core/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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/lombok/eclipse/Eclipse.java b/src/lombok/eclipse/Eclipse.java deleted file mode 100644 index 41d9300f..00000000 --- a/src/lombok/eclipse/Eclipse.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * 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 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 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 AnnotationValues - createAnnotation(Class type, final EclipseNode annotationNode) { - final Annotation annotation = (Annotation) annotationNode.get(); - Map values = new HashMap(); - - final MemberValuePair[] pairs = annotation.memberValuePairs(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); - List raws = new ArrayList(); - List guesses = new ArrayList(); - 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(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/lombok/eclipse/EclipseAST.java b/src/lombok/eclipse/EclipseAST.java deleted file mode 100644 index e42e5de2..00000000 --- a/src/lombok/eclipse/EclipseAST.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * 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 { - /** - * 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 getImportStatements() { - List imports = new ArrayList(); - 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 queuedProblems = new ArrayList(); - - 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 children = buildTypes(top.types); - return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT)); - } - - private void addIfNotNull(Collection collection, EclipseNode n) { - if (n != null) collection.add(n); - } - - private List buildTypes(TypeDeclaration[] children) { - List childNodes = new ArrayList(); - if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type)); - return childNodes; - } - - private EclipseNode buildType(TypeDeclaration type) { - if (setAndGetAsHandled(type)) return null; - List childNodes = new ArrayList(); - 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 buildFields(FieldDeclaration[] children) { - List childNodes = new ArrayList(); - if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child)); - return childNodes; - } - - private static List singleton(T item) { - List list = new ArrayList(); - 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 childNodes = new ArrayList(); - 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 buildMethods(AbstractMethodDeclaration[] children) { - List childNodes = new ArrayList(); - if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method)); - return childNodes; - } - - private EclipseNode buildMethod(AbstractMethodDeclaration method) { - if (setAndGetAsHandled(method)) return null; - List childNodes = new ArrayList(); - 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 buildArguments(Argument[] children) { - List childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - addIfNotNull(childNodes, buildStatement(local.initialization)); - childNodes.addAll(buildAnnotations(local.annotations)); - return putInMap(new EclipseNode(this, local, childNodes, kind)); - } - - private Collection buildAnnotations(Annotation[] annotations) { - List elements = new ArrayList(); - 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 buildStatements(Statement[] children) { - List childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - 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> getStatementTypes() { - return Collections.>singleton(Statement.class); - } -} diff --git a/src/lombok/eclipse/EclipseASTAdapter.java b/src/lombok/eclipse/EclipseASTAdapter.java deleted file mode 100644 index 2062619c..00000000 --- a/src/lombok/eclipse/EclipseASTAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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/lombok/eclipse/EclipseASTVisitor.java b/src/lombok/eclipse/EclipseASTVisitor.java deleted file mode 100644 index 793166d7..00000000 --- a/src/lombok/eclipse/EclipseASTVisitor.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 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("", node.getFileName(), Eclipse.isGenerated(unit) ? " (GENERATED)" : ""); - indent++; - } - - public void endVisitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) { - indent--; - print(""); - } - - public void visitType(EclipseNode node, TypeDeclaration type) { - print("", str(type.name), Eclipse.isGenerated(type) ? " (GENERATED)" : ""); - indent++; - if (printContent) { - print("%s", type); - disablePrinting++; - } - } - - public void visitAnnotationOnType(TypeDeclaration type, EclipseNode node, Annotation annotation) { - forcePrint("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); - } - - public void endVisitType(EclipseNode node, TypeDeclaration type) { - if (printContent) disablePrinting--; - indent--; - print("", 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("", (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance"); - } - - public void visitField(EclipseNode node, FieldDeclaration field) { - print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); - } - - public void endVisitField(EclipseNode node, FieldDeclaration field) { - if (printContent) disablePrinting--; - indent--; - print("", 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("", Eclipse.isGenerated(method) ? " (GENERATED)" : "", annotation); - } - - public void endVisitMethod(EclipseNode node, AbstractMethodDeclaration method) { - if (printContent) disablePrinting--; - String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD"; - indent--; - print("", type, str(method.selector)); - } - - public void visitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { - print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); - } - - public void endVisitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { - indent--; - print("", str(arg.type), str(arg.name)); - } - - public void visitLocal(EclipseNode node, LocalDeclaration local) { - print("", Eclipse.isGenerated(local) ? " (GENERATED)" : "", str(local.type), str(local.name), local.initialization); - indent++; - } - - public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode node, Annotation annotation) { - print("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); - } - - public void endVisitLocal(EclipseNode node, LocalDeclaration local) { - indent--; - print("", 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("", statement.getClass()); - } - } -} diff --git a/src/lombok/eclipse/EclipseAnnotationHandler.java b/src/lombok/eclipse/EclipseAnnotationHandler.java deleted file mode 100644 index aaa57603..00000000 --- a/src/lombok/eclipse/EclipseAnnotationHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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} - * - * 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 { - /** - * 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 annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseNode annotationNode); -} diff --git a/src/lombok/eclipse/EclipseNode.java b/src/lombok/eclipse/EclipseNode.java deleted file mode 100644 index 668e6a6e..00000000 --- a/src/lombok/eclipse/EclipseNode.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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 { - /** {@inheritDoc} */ - EclipseNode(EclipseAST ast, ASTNode node, List 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/lombok/eclipse/HandlerLibrary.java b/src/lombok/eclipse/HandlerLibrary.java deleted file mode 100644 index 36c41504..00000000 --- a/src/lombok/eclipse/HandlerLibrary.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 { - private EclipseAnnotationHandler handler; - private Class annotationClass; - - AnnotationHandlerContainer(EclipseAnnotationHandler handler, Class annotationClass) { - this.handler = handler; - this.annotationClass = annotationClass; - } - - public boolean handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation, - final EclipseNode annotationNode) { - AnnotationValues annValues = Eclipse.createAnnotation(annotationClass, annotationNode); - return handler.handle(annValues, annotation, annotationNode); - } - } - - private Map> annotationHandlers = - new HashMap>(); - - private Collection visitorHandlers = new ArrayList(); - - 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 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 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/lombok/eclipse/TransformEclipseAST.java b/src/lombok/eclipse/TransformEclipseAST.java deleted file mode 100644 index 3b5482ca..00000000 --- a/src/lombok/eclipse/TransformEclipseAST.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/lombok/eclipse/handlers/EclipseHandlerUtil.java deleted file mode 100644 index 2f676d09..00000000 --- a/src/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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 result = new ArrayList(); - 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 createListOfNonExistentFields(List 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 problematic = new ArrayList(); - for (int i = 0 ; i < list.size() ; i++) { - if (!matched[i]) problematic.add(i); - } - - return problematic; - } -} diff --git a/src/lombok/eclipse/handlers/HandleCleanup.java b/src/lombok/eclipse/handlers/HandleCleanup.java deleted file mode 100644 index d296e96b..00000000 --- a/src/lombok/eclipse/handlers/HandleCleanup.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 { - public boolean handle(AnnotationValues 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/lombok/eclipse/handlers/HandleData.java b/src/lombok/eclipse/handlers/HandleData.java deleted file mode 100644 index 8c4e07ce..00000000 --- a/src/lombok/eclipse/handlers/HandleData.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 { - public boolean handle(AnnotationValues 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 nodesForConstructor = new ArrayList(); - 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 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 args = new ArrayList(); - List assigns = new ArrayList(); - List nullChecks = new ArrayList(); - - 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 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 args = new ArrayList(); - List assigns = new ArrayList(); - 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/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java deleted file mode 100644 index 7c0980c8..00000000 --- a/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ /dev/null @@ -1,718 +0,0 @@ -/* - * 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 { - private static final Set BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(Arrays.asList( - "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - - private void checkForBogusFieldNames(EclipseNode type, AnnotationValues 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 annotation, - Annotation ast, EclipseNode annotationNode) { - EqualsAndHashCode ann = annotation.getInstance(); - List excludes = Arrays.asList(ann.exclude()); - List 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 excludes, List 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 nodesForEquality = new ArrayList(); - 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 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 statements = new ArrayList(); - List intoResult = new ArrayList(); - - 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 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 statements = new ArrayList(); - - /* 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/lombok/eclipse/handlers/HandleGetter.java b/src/lombok/eclipse/handlers/HandleGetter.java deleted file mode 100644 index 4a9930e3..00000000 --- a/src/lombok/eclipse/handlers/HandleGetter.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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 { - /** - * 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 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/lombok/eclipse/handlers/HandlePrintAST.java b/src/lombok/eclipse/handlers/HandlePrintAST.java deleted file mode 100644 index 580a54a2..00000000 --- a/src/lombok/eclipse/handlers/HandlePrintAST.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { - public boolean handle(AnnotationValues 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/lombok/eclipse/handlers/HandleSetter.java b/src/lombok/eclipse/handlers/HandleSetter.java deleted file mode 100644 index 9bd10af3..00000000 --- a/src/lombok/eclipse/handlers/HandleSetter.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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 { - /** - * 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 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/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/lombok/eclipse/handlers/HandleSneakyThrows.java deleted file mode 100644 index 38f22b2a..00000000 --- a/src/lombok/eclipse/handlers/HandleSneakyThrows.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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 { - 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 annotation, Annotation source, EclipseNode annotationNode) { - List exceptionNames = annotation.getRawExpressions("value"); - List exceptions = new ArrayList(); - - 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 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 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/lombok/eclipse/handlers/HandleSynchronized.java b/src/lombok/eclipse/handlers/HandleSynchronized.java deleted file mode 100644 index fde36192..00000000 --- a/src/lombok/eclipse/handlers/HandleSynchronized.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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 { - private static final char[] INSTANCE_LOCK_NAME = "$lock".toCharArray(); - private static final char[] STATIC_LOCK_NAME = "$LOCK".toCharArray(); - - @Override public boolean handle(AnnotationValues 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/lombok/eclipse/handlers/HandleToString.java b/src/lombok/eclipse/handlers/HandleToString.java deleted file mode 100644 index d5a4c398..00000000 --- a/src/lombok/eclipse/handlers/HandleToString.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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 { - private void checkForBogusFieldNames(EclipseNode type, AnnotationValues 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 annotation, Annotation ast, EclipseNode annotationNode) { - ToString ann = annotation.getInstance(); - List excludes = Arrays.asList(ann.exclude()); - List 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 excludes, List 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 nodesForToString = new ArrayList(); - 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 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 BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(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/lombok/eclipse/handlers/package-info.java b/src/lombok/eclipse/handlers/package-info.java deleted file mode 100644 index 062b73b3..00000000 --- a/src/lombok/eclipse/handlers/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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/lombok/eclipse/package-info.java b/src/lombok/eclipse/package-info.java deleted file mode 100644 index 0b5add4c..00000000 --- a/src/lombok/eclipse/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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/lombok/installer/AppleNativeLook.java b/src/lombok/installer/AppleNativeLook.java deleted file mode 100644 index 6e64032e..00000000 --- a/src/lombok/installer/AppleNativeLook.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.installer; - -import java.awt.Image; -import java.awt.image.BufferedImage; - -import javax.imageio.ImageIO; - -/** - * Mac OS X specific code to gussy up the GUI a little bit, mostly with a nice dock icon. Well, nicer than - * the standard icon, at any rate. - */ -class AppleNativeLook { - public static void go() throws Exception { - Class appClass = Class.forName("com.apple.eawt.Application"); - Object app = appClass.getMethod("getApplication").invoke(null); - appClass.getMethod("removeAboutMenuItem").invoke(app); - appClass.getMethod("removePreferencesMenuItem").invoke(app); - - BufferedImage image = ImageIO.read(AppleNativeLook.class.getResource("lombokIcon.png")); - appClass.getMethod("setDockIconImage", Image.class).invoke(app, image); - } -} diff --git a/src/lombok/installer/EclipseFinder.java b/src/lombok/installer/EclipseFinder.java deleted file mode 100644 index 8e45852c..00000000 --- a/src/lombok/installer/EclipseFinder.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.installer; - -import static java.util.Arrays.asList; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import lombok.Lombok; -import lombok.core.Version; -import lombok.installer.EclipseLocation.NotAnEclipseException; - -/** Utility class for doing various OS-specific operations related to finding Eclipse installations. */ -class EclipseFinder { - private EclipseFinder() { - //Prevent instantiation. - } - - /** - * Returns a File object pointing to our own jar file. Will obviously fail if the installer was started via - * a jar that wasn't accessed via the file-system, or if its started via e.g. unpacking the jar. - */ - static File findOurJar() { - try { - URI uri = EclipseFinder.class.getResource("/" + EclipseFinder.class.getName().replace('.', '/') + ".class").toURI(); - Pattern p = Pattern.compile("^jar:file:([^\\!]+)\\!.*\\.class$"); - Matcher m = p.matcher(uri.toString()); - if (!m.matches()) return new File("lombok.jar"); - String rawUri = m.group(1); - return new File(URLDecoder.decode(rawUri, Charset.defaultCharset().name())); - } catch (Exception e) { - throw Lombok.sneakyThrow(e); - } - } - - private static final AtomicBoolean windowsDriveInfoLibLoaded = new AtomicBoolean(false); - private static void loadWindowsDriveInfoLib() throws IOException { - if (!windowsDriveInfoLibLoaded.compareAndSet(false, true)) return; - - final String prefix = "lombok-" + Version.getVersion() + "-"; - - File temp = File.createTempFile("lombok", ".mark"); - File dll1 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-i386.dll"); - File dll2 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-x86_64.dll"); - temp.delete(); - dll1.deleteOnExit(); - dll2.deleteOnExit(); - try { - if (unpackDLL("WindowsDriveInfo-i386.dll", dll1)) { - System.load(dll1.getAbsolutePath()); - return; - } - } catch (Throwable ignore) {} - - try { - if (unpackDLL("WindowsDriveInfo-x86_64.dll", dll2)) { - System.load(dll2.getAbsolutePath()); - } - } catch (Throwable ignore) {} - } - - private static boolean unpackDLL(String dllName, File target) throws IOException { - InputStream in = EclipseFinder.class.getResourceAsStream(dllName); - try { - try { - FileOutputStream out = new FileOutputStream(target); - try { - byte[] b = new byte[32000]; - while (true) { - int r = in.read(b); - if (r == -1) break; - out.write(b, 0, r); - } - } finally { - out.close(); - } - } catch (IOException e) { - //Fall through - if there is a file named lombok-WindowsDriveInfo-arch.dll, we'll try it. - return target.exists() && target.canRead(); - } - } finally { - in.close(); - } - - return true; - } - - /** - * Returns all drive letters on windows, regardless of what kind of drive is represented. - * - * @return A List of drive letters, such as ["A", "C", "D", "X"]. - */ - static List getDrivesOnWindows() throws Throwable { - loadWindowsDriveInfoLib(); - - List drives = new ArrayList(); - - WindowsDriveInfo info = new WindowsDriveInfo(); - for (String drive : info.getLogicalDrives()) { - if (info.isFixedDisk(drive)) drives.add(drive); - } - - return drives; - } - - /** - * Returns a list of paths of Eclipse installations. - * Eclipse installations are found by checking for the existence of 'eclipse.exe' in the following locations: - *
        - *
      • X:\*Program Files*\*Eclipse*
      • - *
      • X:\*Eclipse*
      • - *
      - * - * Where 'X' is tried for all local disk drives, unless there's a problem calling fsutil, in which case only - * C: is tried. - */ - private static void findEclipseOnWindows(List locations, List problems) { - List driveLetters = asList("C"); - try { - driveLetters = getDrivesOnWindows(); - } catch (Throwable ignore) { - ignore.printStackTrace(); - } - - //Various try/catch/ignore statements are in this for loop. Weird conditions on the disk can cause exceptions, - //such as an unformatted drive causing a NullPointerException on listFiles. Best action is almost invariably to just - //continue onwards. - for (String letter : driveLetters) { - try { - File f = new File(letter + ":\\"); - for (File dir : f.listFiles()) { - if (!dir.isDirectory()) continue; - try { - if (dir.getName().toLowerCase().contains("eclipse")) { - String eclipseLocation = findEclipseOnWindows1(dir); - if (eclipseLocation != null) { - try { - locations.add(EclipseLocation.create(eclipseLocation)); - } catch (NotAnEclipseException e) { - problems.add(e); - } - } - } - } catch (Exception ignore) {} - - try { - if (dir.getName().toLowerCase().contains("program files")) { - for (File dir2 : dir.listFiles()) { - if (!dir2.isDirectory()) continue; - if (dir.getName().toLowerCase().contains("eclipse")) { - String eclipseLocation = findEclipseOnWindows1(dir); - if (eclipseLocation != null) { - try { - locations.add(EclipseLocation.create(eclipseLocation)); - } catch (NotAnEclipseException e) { - problems.add(e); - } - } - } - } - } - } catch (Exception ignore) {} - } - } catch (Exception ignore) {} - } - } - - /** Checks if the provided directory contains 'eclipse.exe', and if so, returns the directory, otherwise null. */ - private static String findEclipseOnWindows1(File dir) { - if (new File(dir, "eclipse.exe").isFile()) return dir.getAbsolutePath(); - return null; - } - - /** - * Calls the OS-dependent 'find Eclipse' routine. If the local OS doesn't have a routine written for it, - * null is returned. - * - * @param locations - * List of valid eclipse locations - provide an empty list; this - * method will fill it. - * @param problems - * List of eclipse locations that seem to contain half-baked - * eclipses that can't be installed. Provide an empty list; this - * method will fill it. - */ - static void findEclipses(List locations, List problems) { - switch (getOS()) { - case WINDOWS: - findEclipseOnWindows(locations, problems); - break; - case MAC_OS_X: - findEclipseOnMac(locations, problems); - break; - default: - case UNIX: - findEclipseOnUnix(locations, problems); - break; - } - } - - static enum OS { - MAC_OS_X, WINDOWS, UNIX; - } - - static OS getOS() { - String prop = System.getProperty("os.name", "").toLowerCase(); - if (prop.matches("^.*\\bmac\\b.*$")) return OS.MAC_OS_X; - if (prop.matches("^.*\\bdarwin\\b.*$")) return OS.MAC_OS_X; - if (prop.matches("^.*\\bwin(dows)\\b.*$")) return OS.WINDOWS; - - return OS.UNIX; - } - - /** - * Returns the proper name of the executable for the local OS. - * - * @return 'Eclipse.app' on OS X, 'eclipse.exe' on Windows, and 'eclipse' on other OSes. - */ - static String getEclipseExecutableName() { - switch (getOS()) { - case WINDOWS: - return "eclipse.exe"; - case MAC_OS_X: - return "Eclipse.app"; - default: - case UNIX: - return "eclipse"; - } - } - - /** Scans a couple of likely locations on linux. */ - private static void findEclipseOnUnix(List locations, List problems) { - List guesses = new ArrayList(); - - File d; - - d = new File("/usr/bin/eclipse"); - if (d.exists()) guesses.add(d.getPath()); - d = new File("/usr/local/bin/eclipse"); - if (d.exists()) guesses.add(d.getPath()); - d = new File(System.getProperty("user.home", "."), "bin/eclipse"); - if (d.exists()) guesses.add(d.getPath()); - - findEclipseInSubDir("/usr/local/share", guesses); - findEclipseInSubDir("/usr/local", guesses); - findEclipseInSubDir("/usr/share", guesses); - findEclipseInSubDir(System.getProperty("user.home", "."), guesses); - - for (String guess : guesses) { - try { - locations.add(EclipseLocation.create(guess)); - } catch (NotAnEclipseException e) { - problems.add(e); - } - } - } - - private static void findEclipseInSubDir(String dir, List guesses) { - File d = new File(dir); - if (!d.isDirectory()) return; - for (File f : d.listFiles()) { - if (f.isDirectory() && f.getName().toLowerCase().contains("eclipse")) { - File possible = new File(f, "eclipse"); - if (possible.exists()) guesses.add(possible.getAbsolutePath()); - } - } - } - - /** - * Scans /Applications for any folder named 'Eclipse' - */ - private static void findEclipseOnMac(List locations, List problems) { - for (File dir : new File("/Applications").listFiles()) { - if (!dir.isDirectory()) continue; - if (dir.getName().toLowerCase().equals("eclipse.app")) { - //This would be kind of an unorthodox Eclipse installation, but if Eclipse ever - //moves to this more maclike installation concept, our installer can still handle it. - try { - locations.add(EclipseLocation.create("/Applications")); - } catch (NotAnEclipseException e) { - problems.add(e); - } - } - if (dir.getName().toLowerCase().contains("eclipse")) { - if (new File(dir, "Eclipse.app").exists()) { - try { - locations.add(EclipseLocation.create(dir.toString())); - } catch (NotAnEclipseException e) { - problems.add(e); - } - } - } - } - } -} diff --git a/src/lombok/installer/EclipseLocation.java b/src/lombok/installer/EclipseLocation.java deleted file mode 100644 index c43c5042..00000000 --- a/src/lombok/installer/EclipseLocation.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * 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.installer; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.swing.JFrame; -import javax.swing.JOptionPane; - -/** - * Represents an Eclipse installation. - * An instance can figure out if an Eclipse installation has been lombok-ified, and can - * install and uninstall lombok from the Eclipse installation. - */ -final class EclipseLocation { - private static final String OS_NEWLINE; - - static { - String os = System.getProperty("os.name", ""); - - if ("Mac OS".equals(os)) OS_NEWLINE = "\r"; - else if (os.toLowerCase().contains("windows")) OS_NEWLINE = "\r\n"; - else OS_NEWLINE = "\n"; - } - - private final String name; - private final File eclipseIniPath; - private volatile boolean hasLombok; - - /** Toggling the 'selected' checkbox in the GUI is tracked via this boolean */ - boolean selected = true; - - /** - * Thrown when creating a new EclipseLocation with a path object that doesn't, in fact, - * point at an Eclipse installation. - */ - static final class NotAnEclipseException extends Exception { - private static final long serialVersionUID = 1L; - - public NotAnEclipseException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Renders a message dialog with information about what went wrong. - */ - void showDialog(JFrame appWindow) { - JOptionPane.showMessageDialog(appWindow, getMessage(), "Cannot configure Eclipse installation", JOptionPane.WARNING_MESSAGE); - } - } - - private EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws NotAnEclipseException { - this.name = nameOfLocation; - this.eclipseIniPath = pathToEclipseIni; - try { - this.hasLombok = checkForLombok(eclipseIniPath); - } catch (IOException e) { - throw new NotAnEclipseException( - "I can't read the configuration file of the Eclipse installed at " + name + "\n" + - "You may need to run this installer with root privileges if you want to modify that Eclipse.", e); - } - } - - private static final List eclipseExecutableNames = Collections.unmodifiableList(Arrays.asList( - "eclipse.app", "eclipse.exe", "eclipse")); - - /** - * Create a new EclipseLocation by pointing at either the directory contain the Eclipse executable, or the executable itself, - * or an eclipse.ini file. - * - * @throws NotAnEclipseException - * If this isn't an Eclipse executable or a directory with an - * Eclipse executable. - */ - public static EclipseLocation create(String path) throws NotAnEclipseException { - if (path == null) throw new NullPointerException("path"); - File p = new File(path); - - if (!p.exists()) throw new NotAnEclipseException("File does not exist: " + path, null); - if (p.isDirectory()) { - for (String possibleExeName : eclipseExecutableNames) { - File f = new File(p, possibleExeName); - if (f.exists()) return findEclipseIniFromExe(f, 0); - } - - File f = new File(p, "eclipse.ini"); - if (f.exists()) return new EclipseLocation(getFilePath(p), f); - } - - if (p.isFile()) { - if (p.getName().equalsIgnoreCase("eclipse.ini")) { - return new EclipseLocation(getFilePath(p.getParentFile()), p); - } - - if (eclipseExecutableNames.contains(p.getName().toLowerCase())) { - return findEclipseIniFromExe(p, 0); - } - } - - throw new NotAnEclipseException("This path does not appear to contain an Eclipse installation: " + p, null); - } - - private static EclipseLocation findEclipseIniFromExe(File exePath, int loopCounter) throws NotAnEclipseException { - /* Try looking for eclipse.ini as sibling to the executable */ { - File ini = new File(exePath.getParentFile(), "eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); - } - - /* Try looking for Eclipse/app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { - File ini = new File(exePath.getParentFile(), "Eclipse.app/Contents/MacOS/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); - } - - /* If executable is a soft link, follow it and retry. */ { - if (loopCounter < 50) { - try { - String oPath = exePath.getAbsolutePath(); - String nPath = exePath.getCanonicalPath(); - if (!oPath.equals(nPath)) try { - return findEclipseIniFromExe(new File(nPath), loopCounter + 1); - } catch (NotAnEclipseException ignore) { - // Unlinking didn't help find an eclipse, so continue. - } - } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } - } - } - - /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { - String path = exePath.getAbsolutePath(); - try { - path = exePath.getCanonicalPath(); - } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } - - if (path.equals("/usr/bin/eclipse") || path.equals("/bin/eclipse") || path.equals("/usr/local/bin/eclipse")) { - File ini = new File("/usr/lib/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/usr/local/lib/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/usr/local/etc/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/etc/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - } - } - - /* If we get this far, we lose. */ - throw new NotAnEclipseException("This path does not appear to contain an eclipse installation: " + exePath, null); - } - - public static String getFilePath(File p) { - try { - return p.getCanonicalPath(); - } catch (IOException e) { - String x = p.getAbsolutePath(); - return x == null ? p.getPath() : x; - } - } - - @Override public int hashCode() { - return eclipseIniPath.hashCode(); - } - - @Override public boolean equals(Object o) { - if (!(o instanceof EclipseLocation)) return false; - return ((EclipseLocation)o).eclipseIniPath.equals(eclipseIniPath); - } - - /** - * Returns the name of this location; generally the path to the eclipse executable. - * - * Executables: "eclipse.exe" (Windows), "Eclipse.app" (Mac OS X), "eclipse" (Linux and other unixes). - */ - String getName() { - return name; - } - - /** - * @return true if the Eclipse installation has been instrumented with lombok. - */ - boolean hasLombok() { - return hasLombok; - } - - private final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( - "^\\-javaagent\\:.*lombok.*\\.jar$", Pattern.CASE_INSENSITIVE); - - private final Pattern BOOTCLASSPATH_LINE_MATCHER = Pattern.compile( - "^\\-Xbootclasspath\\/a\\:(.*lombok.*\\.jar.*)$", Pattern.CASE_INSENSITIVE); - - private boolean checkForLombok(File iniFile) throws IOException { - if (!iniFile.exists()) return false; - FileInputStream fis = new FileInputStream(iniFile); - try { - BufferedReader br = new BufferedReader(new InputStreamReader(fis)); - String line; - while ((line = br.readLine()) != null) { - if (JAVA_AGENT_LINE_MATCHER.matcher(line.trim()).matches()) return true; - } - - return false; - } finally { - fis.close(); - } - } - - /** Thrown when uninstalling lombok fails. */ - static class UninstallException extends Exception { - private static final long serialVersionUID = 1L; - - public UninstallException(String message, Throwable cause) { - super(message, cause); - } - } - - /** Returns directories that may contain lombok.jar files that need to be deleted. */ - private List getUninstallDirs() { - List result = new ArrayList(); - File x = new File(name); - if (!x.isDirectory()) x = x.getParentFile(); - if (x.isDirectory()) result.add(x); - result.add(eclipseIniPath.getParentFile()); - return result; - } - - /** - * Uninstalls lombok from this location. - * It's a no-op if lombok wasn't there in the first place, - * and it will remove a half-succeeded lombok installation as well. - * - * @throws UninstallException - * If there's an obvious I/O problem that is preventing - * installation. bugs in the uninstall code will probably throw - * other exceptions; this is intentional. - */ - void uninstall() throws UninstallException { - for (File dir : getUninstallDirs()) { - File lombokJar = new File(dir, "lombok.jar"); - if (lombokJar.exists()) { - if (!lombokJar.delete()) throw new UninstallException( - "Can't delete " + lombokJar.getAbsolutePath() + generateWriteErrorMessage(), null); - } - - /* legacy code - lombok at one point used to have a separate jar for the eclipse agent. - * Leave this code in to delete it for those upgrading from an old version. */ { - File agentJar = new File(dir, "lombok.eclipse.agent.jar"); - if (agentJar.exists()) { - if (!agentJar.delete()) throw new UninstallException( - "Can't delete " + agentJar.getAbsolutePath() + generateWriteErrorMessage(), null); - } - } - } - - StringBuilder newContents = new StringBuilder(); - if (eclipseIniPath.exists()) { - try { - FileInputStream fis = new FileInputStream(eclipseIniPath); - try { - BufferedReader br = new BufferedReader(new InputStreamReader(fis)); - String line; - while ((line = br.readLine()) != null) { - if (JAVA_AGENT_LINE_MATCHER.matcher(line).matches()) continue; - Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); - if (m.matches()) { - StringBuilder elemBuilder = new StringBuilder(); - elemBuilder.append("-Xbootclasspath/a:"); - boolean first = true; - for (String elem : m.group(1).split(Pattern.quote(File.pathSeparator))) { - if (elem.toLowerCase().endsWith("lombok.jar")) continue; - /* legacy code -see previous comment that starts with 'legacy' */ { - if (elem.toLowerCase().endsWith("lombok.eclipse.agent.jar")) continue; - } - if (first) first = false; - else elemBuilder.append(File.pathSeparator); - elemBuilder.append(elem); - } - if (!first) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); - continue; - } - - newContents.append(line).append(OS_NEWLINE); - } - - } finally { - fis.close(); - } - - FileOutputStream fos = new FileOutputStream(eclipseIniPath); - try { - fos.write(newContents.toString().getBytes()); - } finally { - fos.close(); - } - } catch (IOException e) { - throw new UninstallException("Cannot uninstall lombok from " + name + generateWriteErrorMessage(), e); - } - } - } - - /** Thrown when installing lombok fails. */ - static class InstallException extends Exception { - private static final long serialVersionUID = 1L; - - public InstallException(String message, Throwable cause) { - super(message, cause); - } - } - - private static String generateWriteErrorMessage() { - String osSpecificError; - - switch (EclipseFinder.getOS()) { - default: - case MAC_OS_X: - case UNIX: - osSpecificError = ":\nStart terminal, go to the directory with lombok.jar, and run: sudo java -jar lombok.jar"; - break; - case WINDOWS: - osSpecificError = ":\nStart a new cmd (dos box) with admin privileges, go to the directory with lombok.jar, and run: java -jar lombok.jar"; - break; - } - - return ", probably because this installer does not have the access rights.\n" + - "Try re-running the installer with administrative privileges" + osSpecificError; - } - - /** - * Install lombok into the Eclipse at this location. - * If lombok is already there, it is overwritten neatly (upgrade mode). - * - * @throws InstallException - * If there's an obvious I/O problem that is preventing - * installation. bugs in the install code will probably throw - * other exceptions; this is intentional. - */ - void install() throws InstallException { - // For whatever reason, relative paths in your eclipse.ini file don't work on linux, but only for -javaagent. - // If someone knows how to fix this, please do so, as this current hack solution (putting the absolute path - // to the jar files in your eclipse.ini) means you can't move your eclipse around on linux without lombok - // breaking it. NB: rerunning lombok.jar installer and hitting 'update' will fix it if you do that. - boolean fullPathRequired = EclipseFinder.getOS() == EclipseFinder.OS.UNIX; - - boolean installSucceeded = false; - StringBuilder newContents = new StringBuilder(); - //If 'installSucceeded' is true here, something very weird is going on, but instrumenting all of them - //is no less bad than aborting, and this situation should be rare to the point of non-existence. - - File lombokJar = new File(eclipseIniPath.getParentFile(), "lombok.jar"); - - File ourJar = EclipseFinder.findOurJar(); - byte[] b = new byte[524288]; - boolean readSucceeded = true; - try { - FileOutputStream out = new FileOutputStream(lombokJar); - try { - readSucceeded = false; - InputStream in = new FileInputStream(ourJar); - try { - while (true) { - int r = in.read(b); - if (r == -1) break; - if (r > 0) readSucceeded = true; - out.write(b, 0, r); - } - } finally { - in.close(); - } - } finally { - out.close(); - } - } catch (IOException e) { - try { - lombokJar.delete(); - } catch (Throwable ignore) { /* Nothing we can do about that. */ } - if (!readSucceeded) throw new InstallException( - "I can't read my own jar file. I think you've found a bug in this installer!\nI suggest you restart it " + - "and use the 'what do I do' link, to manually install lombok. Also, tell us about this at:\n" + - "http://groups.google.com/group/project-lombok - Thanks!", e); - throw new InstallException("I can't write to your Eclipse directory at " + name + generateWriteErrorMessage(), e); - } - - /* legacy - delete lombok.eclipse.agent.jar if its there, which lombok no longer uses. */ { - new File(lombokJar.getParentFile(), "lombok.eclipse.agent.jar").delete(); - } - - try { - FileInputStream fis = new FileInputStream(eclipseIniPath); - try { - BufferedReader br = new BufferedReader(new InputStreamReader(fis)); - String line; - while ((line = br.readLine()) != null) { - if (JAVA_AGENT_LINE_MATCHER.matcher(line).matches()) continue; - Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); - if (m.matches()) { - StringBuilder elemBuilder = new StringBuilder(); - elemBuilder.append("-Xbootclasspath/a:"); - boolean first = true; - for (String elem : m.group(1).split(Pattern.quote(File.pathSeparator))) { - if (elem.toLowerCase().endsWith("lombok.jar")) continue; - /* legacy code -see previous comment that starts with 'legacy' */ { - if (elem.toLowerCase().endsWith("lombok.eclipse.agent.jar")) continue; - } - if (first) first = false; - else elemBuilder.append(File.pathSeparator); - elemBuilder.append(elem); - } - if (!first) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); - continue; - } - - newContents.append(line).append(OS_NEWLINE); - } - - } finally { - fis.close(); - } - - String fullPathToLombok = fullPathRequired ? (lombokJar.getParentFile().getCanonicalPath() + File.separator) : ""; - - newContents.append(String.format( - "-javaagent:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); - newContents.append(String.format( - "-Xbootclasspath/a:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); - - FileOutputStream fos = new FileOutputStream(eclipseIniPath); - try { - fos.write(newContents.toString().getBytes()); - } finally { - fos.close(); - } - installSucceeded = true; - } catch (IOException e) { - throw new InstallException("Cannot install lombok at " + name + generateWriteErrorMessage(), e); - } finally { - if (!installSucceeded) try { - lombokJar.delete(); - } catch (Throwable ignore) {} - } - - if (!installSucceeded) { - throw new InstallException("I can't find the eclipse.ini file. Is this a real Eclipse installation?", null); - } - } -} diff --git a/src/lombok/installer/Installer.java b/src/lombok/installer/Installer.java deleted file mode 100644 index e1da5d31..00000000 --- a/src/lombok/installer/Installer.java +++ /dev/null @@ -1,895 +0,0 @@ -/* - * 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.installer; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.FileDialog; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.HeadlessException; -import java.awt.Insets; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.font.TextAttribute; -import java.io.File; -import java.io.FilenameFilter; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.Scrollable; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.filechooser.FileFilter; - -import lombok.core.Version; -import lombok.installer.EclipseFinder.OS; -import lombok.installer.EclipseLocation.InstallException; -import lombok.installer.EclipseLocation.NotAnEclipseException; -import lombok.installer.EclipseLocation.UninstallException; - -/** - * The lombok installer proper. - * Uses swing to show a simple GUI that can add and remove the java agent to Eclipse installations. - * Also offers info on what this installer does in case people want to instrument their Eclipse manually, - * and looks in some common places on Mac OS X and Windows. - */ -public class Installer { - private static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); - - private JFrame appWindow; - - private JComponent loadingExpl; - - private Component javacArea; - private Component eclipseArea; - private Component uninstallArea; - private Component howIWorkArea; - - private Box uninstallBox; - private List toUninstall; - - private JHyperLink uninstallButton; - private JLabel uninstallPlaceholder; - private JButton installButton; - - public static void main(String[] args) { - if (args.length > 0 && (args[0].equals("install") || args[0].equals("uninstall"))) { - boolean uninstall = args[0].equals("uninstall"); - if (args.length < 3 || !args[1].equals("eclipse")) { - System.err.printf("Run java -jar lombok.jar %1$s eclipse path/to/eclipse/executable (or 'auto' to %1$s to all auto-discovered eclipse locations)\n", uninstall ? "uninstall" : "install"); - System.exit(1); - } - String path = args[2]; - try { - final List locations = new ArrayList(); - final List problems = new ArrayList(); - if (path.equals("auto")) { - EclipseFinder.findEclipses(locations, problems); - } else { - locations.add(EclipseLocation.create(path)); - } - int validLocations = locations.size(); - for (EclipseLocation loc : locations) { - try { - if (uninstall) { - loc.uninstall(); - } else { - loc.install(); - } - System.out.printf("Lombok %s %s: %s\n", uninstall ? "uninstalled" : "installed", uninstall ? "from" : "to", loc.getName()); - } catch (InstallException e) { - System.err.printf("Installation at %s failed:\n", loc.getName()); - System.err.println(e.getMessage()); - validLocations--; - } catch (UninstallException e) { - System.err.printf("Uninstall at %s failed:\n", loc.getName()); - System.err.println(e.getMessage()); - validLocations--; - } - } - for (NotAnEclipseException problem : problems) { - System.err.println("WARNING: " + problem.getMessage()); - } - if (validLocations == 0) { - System.err.println("WARNING: Zero valid locations found; so nothing was done."); - } - System.exit(0); - } catch (NotAnEclipseException e) { - System.err.println("Not a valid eclipse location:"); - System.err.println(e.getMessage()); - System.exit(2); - } - } - - if (args.length > 0 && args[0].equals("uninstall")) { - if (args.length < 3 || !args[1].equals("eclipse")) { - System.err.println("Run java -jar lombok.jar uninstall eclipse path/to/eclipse/executable (or 'auto' to uninstall all auto-discovered eclipse locations)"); - System.exit(1); - } - String path = args[2]; - try { - EclipseLocation loc = EclipseLocation.create(path); - loc.uninstall(); - System.out.println("Uninstalled from: " + loc.getName()); - System.exit(0); - } catch (NotAnEclipseException e) { - System.err.println("Not a valid eclipse location:"); - System.err.println(e.getMessage()); - System.exit(2); - } catch (UninstallException e) { - System.err.println("Uninstall failed:"); - System.err.println(e.getMessage()); - System.exit(1); - } - } - - if (EclipseFinder.getOS() == OS.MAC_OS_X) { - System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Lombok Installer"); - System.setProperty("com.apple.macos.use-file-dialog-packages", "true"); - } - - try { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception ignore) {} - - new Installer().show(); - } catch (HeadlessException e) { - printHeadlessInfo(); - } - } - }); - } catch (HeadlessException e) { - printHeadlessInfo(); - } - } - - /** - * If run in headless mode, the installer can't show its fancy GUI. There's little point in running - * the installer without a GUI environment, as Eclipse doesn't run in headless mode either, so - * we'll make do with showing some basic info on Lombok as well as instructions for using lombok with javac. - */ - private static void printHeadlessInfo() { - System.out.printf("About lombok v%s\n" + - "Lombok makes java better by providing very spicy additions to the Java programming language," + - "such as using @Getter to automatically generate a getter method for any field.\n\n" + - "Browse to %s for more information. To install lombok on Eclipse, re-run this jar file on a " + - "graphical computer system - this message is being shown because your terminal is not graphics capable." + - "If you are just using 'javac' or a tool that calls on javac, no installation is neccessary; just " + - "make sure lombok.jar is in the classpath when you compile. Example:\n\n" + - " java -cp lombok.jar MyCode.java\n\n\n" + - "If for whatever reason you can't run the graphical installer but you do want to install lombok into eclipse," + - "start this jar with the following syntax:\n\n" + - " java -jar lombok.jar install eclipse path/to/your/eclipse/executable", Version.getVersion(), ABOUT_LOMBOK_URL); - } - - /** - * Creates a new installer that starts out invisible. - * Call the {@link #show()} method on a freshly created installer to render it. - */ - public Installer() { - appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); - - appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - appWindow.setResizable(false); - appWindow.setIconImage(Toolkit.getDefaultToolkit().getImage(Installer.class.getResource("lombokIcon.png"))); - - try { - javacArea = buildJavacArea(); - eclipseArea = buildEclipseArea(); - uninstallArea = buildUninstallArea(); - uninstallArea.setVisible(false); - howIWorkArea = buildHowIWorkArea(); - howIWorkArea.setVisible(false); - buildChrome(appWindow.getContentPane()); - appWindow.pack(); - } catch (Throwable t) { - handleException(t); - } - } - - private void handleException(final Throwable t) { - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, "There was a problem during the installation process:\n" + t, "Uh Oh!", JOptionPane.ERROR_MESSAGE); - t.printStackTrace(); - System.exit(1); - } - }); - } - - private Component buildHowIWorkArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - container.add(new JLabel(HOW_I_WORK_TITLE), constraints); - - constraints.gridy = 1; - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(String.format(HOW_I_WORK_EXPLANATION, File.pathSeparator)), constraints); - - Box buttonBar = Box.createHorizontalBox(); - JButton backButton = new JButton("Okay - Good to know!"); - buttonBar.add(Box.createHorizontalGlue()); - buttonBar.add(backButton); - - backButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - howIWorkArea.setVisible(false); - javacArea.setVisible(true); - eclipseArea.setVisible(true); - appWindow.pack(); - } - }); - - constraints.gridy = 2; - container.add(buttonBar, constraints); - - return container; - } - - private Component buildUninstallArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - container.add(new JLabel(UNINSTALL_TITLE), constraints); - - constraints.gridy = 1; - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(UNINSTALL_EXPLANATION), constraints); - - uninstallBox = Box.createVerticalBox(); - constraints.gridy = 2; - constraints.fill = GridBagConstraints.HORIZONTAL; - container.add(uninstallBox, constraints); - - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridy = 3; - container.add(new JLabel("Are you sure?"), constraints); - - Box buttonBar = Box.createHorizontalBox(); - JButton noButton = new JButton("No - Don't uninstall"); - buttonBar.add(noButton); - buttonBar.add(Box.createHorizontalGlue()); - JButton yesButton = new JButton("Yes - uninstall Lombok"); - buttonBar.add(yesButton); - - noButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - uninstallArea.setVisible(false); - javacArea.setVisible(true); - eclipseArea.setVisible(true); - appWindow.pack(); - } - }); - - yesButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - doUninstall(); - } - }); - - constraints.gridy = 4; - container.add(buttonBar, constraints); - - return container; - } - - private Component buildJavacArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.insets = new Insets(8, 0, 0, 16); - - container.add(new JLabel(JAVAC_TITLE), constraints); - - constraints.gridy = 1; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - container.add(new JLabel(JAVAC_EXPLANATION), constraints); - - JLabel example = new JLabel(JAVAC_EXAMPLE); - - constraints.gridy = 2; - container.add(example, constraints); - return container; - } - - private Component buildEclipseArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(ECLIPSE_TITLE), constraints); - - constraints.gridy = 1; - container.add(new JLabel(ECLIPSE_EXPLANATION), constraints); - - constraints.gridy = 2; - loadingExpl = Box.createHorizontalBox(); - loadingExpl.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - loadingExpl.add(new JLabel(ECLIPSE_LOADING_EXPLANATION)); - container.add(loadingExpl, constraints); - - constraints.weightx = 1.0; - constraints.gridy = 3; - constraints.fill = GridBagConstraints.HORIZONTAL; - eclipsesList = new EclipsesList(); - - JScrollPane eclipsesListScroll = new JScrollPane(eclipsesList); - eclipsesListScroll.setBackground(Color.WHITE); - eclipsesListScroll.getViewport().setBackground(Color.WHITE); - container.add(eclipsesListScroll, constraints); - - Thread findEclipsesThread = new Thread() { - @Override public void run() { - try { - final List locations = new ArrayList(); - final List problems = new ArrayList(); - EclipseFinder.findEclipses(locations, problems); - - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - for (EclipseLocation location : locations) { - try { - eclipsesList.addEclipse(location); - } catch (Throwable t) { - handleException(t); - } - } - - for (NotAnEclipseException problem : problems) { - problem.showDialog(appWindow); - } - - loadingExpl.setVisible(false); - - if (locations.size() + problems.size() == 0) { - JOptionPane.showMessageDialog(appWindow, - "I don't know how to automatically find Eclipse installations on this platform.\n" + - "Please use the 'Specify Eclipse Location...' button to manually point out the\n" + - "location of your Eclipse installation to me. Thanks!", "Can't find Eclipse", JOptionPane.INFORMATION_MESSAGE); - } - } - }); - } catch (Throwable t) { - handleException(t); - } - } - }; - - findEclipsesThread.start(); - - Box buttonBar = Box.createHorizontalBox(); - JButton specifyEclipseLocationButton = new JButton("Specify Eclipse location..."); - buttonBar.add(specifyEclipseLocationButton); - specifyEclipseLocationButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent event) { - final String exeName = EclipseFinder.getEclipseExecutableName(); - String file = null; - - if (EclipseFinder.getOS() == OS.MAC_OS_X) { - FileDialog chooser = new FileDialog(appWindow); - chooser.setMode(FileDialog.LOAD); - chooser.setFilenameFilter(new FilenameFilter() { - @Override public boolean accept(File dir, String fileName) { - if (exeName.equalsIgnoreCase(fileName)) return true; - if (new File(dir, fileName).isDirectory()) return true; - return false; - } - }); - - chooser.setVisible(true); - file = new File(chooser.getDirectory(), chooser.getFile()).getAbsolutePath(); - } else { - JFileChooser chooser = new JFileChooser(); - - chooser.setAcceptAllFileFilterUsed(false); - chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - chooser.setFileFilter(new FileFilter() { - @Override public boolean accept(File f) { - if (f.getName().equalsIgnoreCase(exeName)) return true; - if (f.getName().equalsIgnoreCase("eclipse.ini")) return true; - if (f.isDirectory()) return true; - - return false; - } - - @Override public String getDescription() { - return "Eclipse Installation"; - } - }); - - switch (chooser.showDialog(appWindow, "Select")) { - case JFileChooser.APPROVE_OPTION: - file = chooser.getSelectedFile().getAbsolutePath(); - } - } - - if (file != null) { - try { - eclipsesList.addEclipse(EclipseLocation.create(file)); - } catch (NotAnEclipseException e) { - e.showDialog(appWindow); - } catch (Throwable t) { - handleException(t); - } - } - } - }); - - buttonBar.add(Box.createHorizontalGlue()); - installButton = new JButton("Install / Update"); - buttonBar.add(installButton); - - installButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - List locationsToInstall = new ArrayList(eclipsesList.getSelectedEclipses()); - if (locationsToInstall.isEmpty()) { - JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations!.", "No Selection", JOptionPane.WARNING_MESSAGE); - return; - } - - install(locationsToInstall); - } - }); - - constraints.gridy = 4; - constraints.weightx = 0; - container.add(buttonBar, constraints); - - constraints.gridy = 5; - constraints.fill = GridBagConstraints.NONE; - JHyperLink showMe = new JHyperLink("Show me what this installer will do to my Eclipse installation."); - container.add(showMe, constraints); - showMe.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - showWhatIDo(); - } - }); - - constraints.gridy = 6; - uninstallButton = new JHyperLink("Uninstall lombok from selected Eclipse installations."); - uninstallPlaceholder = new JLabel(" "); - uninstallButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - List locationsToUninstall = new ArrayList(); - for (EclipseLocation location : eclipsesList.getSelectedEclipses()) { - if (location.hasLombok()) locationsToUninstall.add(location); - } - - if (locationsToUninstall.isEmpty()) { - JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations that have been lombok-enabled.", "No Selection", JOptionPane.WARNING_MESSAGE); - return; - } - - - uninstall(locationsToUninstall); - } - }); - container.add(uninstallButton, constraints); - uninstallPlaceholder.setVisible(false); - container.add(uninstallPlaceholder, constraints); - - - return container; - } - - private void showWhatIDo() { - javacArea.setVisible(false); - eclipseArea.setVisible(false); - howIWorkArea.setVisible(true); - appWindow.pack(); - } - - private void uninstall(List locations) { - javacArea.setVisible(false); - eclipseArea.setVisible(false); - - uninstallBox.removeAll(); - uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); - for (EclipseLocation location : locations) { - JLabel label = new JLabel(location.getName()); - label.setFont(label.getFont().deriveFont(Font.BOLD)); - uninstallBox.add(label); - } - uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); - - toUninstall = locations; - uninstallArea.setVisible(true); - appWindow.pack(); - } - - private void install(final List toInstall) { - JPanel spinner = new JPanel(); - spinner.setOpaque(true); - spinner.setLayout(new FlowLayout()); - spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - appWindow.setContentPane(spinner); - - final AtomicReference success = new AtomicReference(true); - - new Thread() { - @Override public void run() { - for (EclipseLocation loc : toInstall) { - try { - loc.install(); - } catch (final InstallException e) { - success.set(false); - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - e.getMessage(), "Install Problem", JOptionPane.ERROR_MESSAGE); - } - }); - } catch (Exception e2) { - //Shouldn't happen. - throw new RuntimeException(e2); - } - } - } - - if (success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - "Lombok has been installed on the selected Eclipse installations.
      " + - "Don't forget to add lombok.jar to your projects, and restart your eclipse!
      " + - "If you start eclipse with a custom -vm parameter, you'll need to add:
      " + - "-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar
      " + - "as parameter as well.", "Install successful", - JOptionPane.INFORMATION_MESSAGE); - appWindow.setVisible(false); - System.exit(0); - } - }); - - if (!success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - System.exit(0); - } - }); - } - }.start(); - } - - private void doUninstall() { - JPanel spinner = new JPanel(); - spinner.setOpaque(true); - spinner.setLayout(new FlowLayout()); - spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - - appWindow.setContentPane(spinner); - - final AtomicReference success = new AtomicReference(true); - new Thread() { - @Override public void run() { - for (EclipseLocation loc : toUninstall) { - try { - loc.uninstall(); - } catch (final UninstallException e) { - success.set(false); - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - e.getMessage(), "Uninstall Problem", JOptionPane.ERROR_MESSAGE); - } - }); - } catch (Exception e2) { - //Shouldn't happen. - throw new RuntimeException(e2); - } - } - } - - if (success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, "Lombok has been removed from the selected Eclipse installations.", "Uninstall successful", JOptionPane.INFORMATION_MESSAGE); - appWindow.setVisible(false); - System.exit(0); - } - }); - } - }.start(); - } - - private EclipsesList eclipsesList = new EclipsesList(); - - private static class JHyperLink extends JButton { - private static final long serialVersionUID = 1L; - - public JHyperLink(String text) { - super(); - setFont(getFont().deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, 1))); - setText(text); - setBorder(null); - setContentAreaFilled(false); - setForeground(Color.BLUE); - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - setMargin(new Insets(0, 0, 0, 0)); - } - } - - void selectedLomboksChanged(List selectedEclipses) { - boolean uninstallAvailable = false; - boolean installAvailable = false; - for (EclipseLocation loc : selectedEclipses) { - if (loc.hasLombok()) uninstallAvailable = true; - installAvailable = true; - } - - uninstallButton.setVisible(uninstallAvailable); - uninstallPlaceholder.setVisible(!uninstallAvailable); - installButton.setEnabled(installAvailable); - } - - private class EclipsesList extends JPanel implements Scrollable { - private static final long serialVersionUID = 1L; - - List locations = new ArrayList(); - - EclipsesList() { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - setBackground(Color.WHITE); - } - - List getSelectedEclipses() { - List list = new ArrayList(); - for (EclipseLocation loc : locations) if (loc.selected) list.add(loc); - return list; - } - - void fireSelectionChange() { - selectedLomboksChanged(getSelectedEclipses()); - } - - void addEclipse(final EclipseLocation location) { - if (locations.contains(location)) return; - Box box = Box.createHorizontalBox(); - box.setBackground(Color.WHITE); - final JCheckBox checkbox = new JCheckBox(location.getName()); - checkbox.setBackground(Color.WHITE); - box.add(checkbox); - checkbox.setSelected(true); - checkbox.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - location.selected = checkbox.isSelected(); - fireSelectionChange(); - } - }); - - if (location.hasLombok()) { - box.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombokIcon.png")))); - } - box.add(Box.createHorizontalGlue()); - locations.add(location); - add(box); - getParent().doLayout(); - fireSelectionChange(); - } - - @Override public Dimension getPreferredScrollableViewportSize() { - return new Dimension(1, 100); - } - - @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - return 12; - } - - @Override public boolean getScrollableTracksViewportHeight() { - return false; - } - - @Override public boolean getScrollableTracksViewportWidth() { - return true; - } - - @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - return 1; - } - } - - private void buildChrome(Container appWindowContainer) { - JLabel leftGraphic = new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombok.png"))); - - GridBagConstraints constraints = new GridBagConstraints(); - - appWindowContainer.setLayout(new GridBagLayout()); - - constraints.gridheight = 3; - constraints.gridwidth = 1; - constraints.gridx = 0; - constraints.gridy = 0; - constraints.insets = new Insets(8, 8, 8, 8); - appWindowContainer.add(leftGraphic, constraints); - constraints.insets = new Insets(0, 0, 0, 0); - - constraints.gridx++; - constraints.gridy++; - constraints.gridheight = 1; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.ipadx = 16; - constraints.ipady = 14; - appWindowContainer.add(javacArea, constraints); - - constraints.gridy++; - appWindowContainer.add(eclipseArea, constraints); - - appWindowContainer.add(uninstallArea, constraints); - - appWindowContainer.add(howIWorkArea, constraints); - - constraints.gridy++; - constraints.gridwidth = 2; - constraints.gridx = 0; - constraints.weightx = 0; - constraints.weighty = 0; - constraints.ipadx = 0; - constraints.ipady = 0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.SOUTHEAST; - constraints.insets = new Insets(0, 16, 8, 8); - Box buttonBar = Box.createHorizontalBox(); - JButton quitButton = new JButton("Quit Installer"); - quitButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - appWindow.setVisible(false); - System.exit(0); - } - }); - final JHyperLink hyperlink = new JHyperLink(ABOUT_LOMBOK_URL.toString()); - hyperlink.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent event) { - hyperlink.setForeground(new Color(85, 145, 90)); - try { - //java.awt.Desktop doesn't exist in 1.5. - Object desktop = Class.forName("java.awt.Desktop").getMethod("getDesktop").invoke(null); - Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, ABOUT_LOMBOK_URL); - } catch (Exception e) { - Runtime rt = Runtime.getRuntime(); - try { - switch (EclipseFinder.getOS()) { - case WINDOWS: - String[] cmd = new String[4]; - cmd[0] = "cmd.exe"; - cmd[1] = "/C"; - cmd[2] = "start"; - cmd[3] = ABOUT_LOMBOK_URL.toString(); - rt.exec(cmd); - break; - case MAC_OS_X: - rt.exec("open " + ABOUT_LOMBOK_URL.toString()); - break; - default: - case UNIX: - rt.exec("firefox " + ABOUT_LOMBOK_URL.toString()); - break; - } - } catch (Exception e2) { - JOptionPane.showMessageDialog(appWindow, - "Well, this is embarrassing. I don't know how to open a webbrowser.\n" + - "I guess you'll have to open it. Browse to:\n" + - "http://projectlombok.org for more information about Lombok.", - "I'm embarrassed", JOptionPane.INFORMATION_MESSAGE); - } - } - } - }); - buttonBar.add(hyperlink); - buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); - buttonBar.add(new JLabel("v" + Version.getVersion() + "")); - - buttonBar.add(Box.createHorizontalGlue()); - buttonBar.add(quitButton); - appWindow.add(buttonBar, constraints); - } - - /** - * Makes the installer window visible. - */ - public void show() { - appWindow.setVisible(true); - if (EclipseFinder.getOS() == OS.MAC_OS_X) { - try { - AppleNativeLook.go(); - } catch (Throwable ignore) { - //We're just prettying up the app. If it fails, meh. - } - } - } - - private static final String ECLIPSE_TITLE = - "Eclipse"; - - private static final String ECLIPSE_EXPLANATION = - "Lombok can update your Eclipse to fully support all Lombok features.
      " + - "Select Eclipse installations below and hit 'Install/Update'."; - - private static final String ECLIPSE_LOADING_EXPLANATION = - "Scanning your drives for Eclipse installations..."; - - private static final String JAVAC_TITLE = - "Javac       (and tools that invoke javac such as ant and maven)"; - - private static final String JAVAC_EXPLANATION = - "Lombok works 'out of the box' with javac.
      Just make sure the lombok.jar is in your classpath when you compile."; - - private static final String JAVAC_EXAMPLE = - "Example: javac -cp lombok.jar MyCode.java"; - - private static final String UNINSTALL_TITLE = - "Uninstall"; - - private static final String UNINSTALL_EXPLANATION = - "Uninstall Lombok from the following Eclipse Installations?"; - - private static final String HOW_I_WORK_TITLE = - "What this installer does"; - - private static final String HOW_I_WORK_EXPLANATION = - "
        " + - "
      1. First, I copy myself (lombok.jar) to your Eclipse install directory.
      2. " + - "
      3. Then, I edit the eclipse.ini file to add the following two entries:
        " + - "
        -Xbootclasspath/a:lombok.jar
        " + - "-javaagent:lombok.jar
      " + - "
      " + - "That's all there is to it. Note that on Mac OS X, eclipse.ini is hidden in
      " + - "Eclipse.app%1$sContents%1$sMacOS so that's where I place the jar files."; -} diff --git a/src/lombok/installer/WindowsDriveInfo-i386.dll b/src/lombok/installer/WindowsDriveInfo-i386.dll deleted file mode 100644 index eb7fa49a..00000000 Binary files a/src/lombok/installer/WindowsDriveInfo-i386.dll and /dev/null differ diff --git a/src/lombok/installer/WindowsDriveInfo-x86_64.dll b/src/lombok/installer/WindowsDriveInfo-x86_64.dll deleted file mode 100644 index 0b7c9a83..00000000 Binary files a/src/lombok/installer/WindowsDriveInfo-x86_64.dll and /dev/null differ diff --git a/src/lombok/installer/WindowsDriveInfo.java b/src/lombok/installer/WindowsDriveInfo.java deleted file mode 100644 index 41a6b17e..00000000 --- a/src/lombok/installer/WindowsDriveInfo.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.installer; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class uses native calls on windows to figure out all drives, - * and, for each drive, if its a harddisk or something else. - * - * The output is essentially equivalent to running windows executable: - *
      fsutil fsinfo drives
      - * and - *
      fsutil fsinfo drivetype C:
      - * - * except that (A) fsutil requires privileges, (B) someone might have moved - * it out of the path or some such, and (C) its output is internationalized, - * so unless you want to include a table of how to say "Fixed Disk" in 300 - * languages, this really is a superior solution. - *

      - * To compile it, you'll need windows, as well as MinGW: - * http://sourceforge.net/projects/mingw/files/ - *

      - * Fetch gcc 4.0.4+, you don't need anything extra. Toss /c/mingw/bin in - * your git bash prompt's path (/etc/profile) and then run: - * - * $ gcc -c \ - -I "/c/Program Files/Java/jdk1.6.0_14/include" \ - -I "/c/Program Files/Java/jdk1.6.0_14/include/win32" \ - -D__int64="long long" lombok_installer_WindowsDriveInfo.c - * - * $ dllwrap.exe --add-stdcall-alias \ - -o WindowsDriveInfo-i386.dll \ - lombok_installer_WindowsDriveInfo.o - * - * You may get a warning along the lines of "Creating an export definition". - * This is expected behaviour. - * - *

      - * Now download MinGW-w64 to build the 64-bit version of the dll (you thought you were done, weren't you?) - * from: http://sourceforge.net/projects/mingw-w64/files/ - * (under toolchains targetting Win64 / Release for GCC 4.4.0 (or later) / the version for your OS.) - * - * Then, do this all over again, but this time with the x86_64-w64-mingw32-gcc and - * x86_64-w64-mingw32-dllwrap versions that are part of the MinGW-w64 distribution. - * Name the dll 'WindowsDriveInfo-x86_64.dll'. - * - * Both the 32-bit and 64-bit DLLs that this produces have been checked into the git repository - * under src/lombok/installer so you won't need to build them again unless you make some changes to - * the code in the winsrc directory. - */ -public class WindowsDriveInfo { - /** - * Return a list of all available drive letters, such as ["A", "C", "D"]. - */ - public List getLogicalDrives() { - int flags = getLogicalDrives0(); - - List letters = new ArrayList(); - for (int i = 0; i < 26; i++) { - if ((flags & (1 << i)) != 0) letters.add(Character.toString((char)('A' + i))); - } - - return letters; - } - - /** - * Calls kernel32's GetLogicalDrives, which returns an int containing - * flags; bit 0 corresponds to drive A, bit 25 to drive Z. on = disk exists. - */ - private native int getLogicalDrives0(); - - /** - * Feed it a drive letter (such as 'A') to see if it is a fixed disk. - */ - public boolean isFixedDisk(String letter) { - if (letter.length() != 1) throw new IllegalArgumentException("Supply 1 letter, not: " + letter); - char drive = Character.toUpperCase(letter.charAt(0)); - if (drive < 'A' || drive > 'Z') throw new IllegalArgumentException( - "A drive is indicated by a letter, so A-Z inclusive. Not " + drive); - return getDriveType(drive + ":\\") == 3L; - } - - /** - * Mirror of kernel32's GetDriveTypeA. You must pass in 'A:\\' - - * so including both a colon and a backslash! - * - * 0 = error - * 1 = doesn't exist - * 2 = removable drive - * 3 = fixed disk - * 4 = remote (network) disk - * 5 = cd-rom - * 6 = ram disk - */ - private native int getDriveType(String name); - - public static void main(String[] args) { - System.loadLibrary("WindowsDriveInfo"); - WindowsDriveInfo info = new WindowsDriveInfo(); - - for (String letter : info.getLogicalDrives()) { - System.out.printf("Drive %s: - %s\n", letter, - info.isFixedDisk(letter) ? "Fixed Disk" : "Not Fixed Disk"); - } - } -} diff --git a/src/lombok/installer/loading.gif b/src/lombok/installer/loading.gif deleted file mode 100644 index b9fc304a..00000000 Binary files a/src/lombok/installer/loading.gif and /dev/null differ diff --git a/src/lombok/installer/lombok.png b/src/lombok/installer/lombok.png deleted file mode 100644 index d4efde04..00000000 Binary files a/src/lombok/installer/lombok.png and /dev/null differ diff --git a/src/lombok/installer/lombok.svg b/src/lombok/installer/lombok.svg deleted file mode 100644 index 0d561aea..00000000 --- a/src/lombok/installer/lombok.svg +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/lombok/installer/lombokIcon.png b/src/lombok/installer/lombokIcon.png deleted file mode 100644 index 48fd4307..00000000 Binary files a/src/lombok/installer/lombokIcon.png and /dev/null differ diff --git a/src/lombok/installer/lombokText.png b/src/lombok/installer/lombokText.png deleted file mode 100644 index 279746cb..00000000 Binary files a/src/lombok/installer/lombokText.png and /dev/null differ diff --git a/src/lombok/installer/lombokText.svg b/src/lombok/installer/lombokText.svg deleted file mode 100644 index 9fd2f73b..00000000 --- a/src/lombok/installer/lombokText.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/lombok/installer/package-info.java b/src/lombok/installer/package-info.java deleted file mode 100644 index 14b329b4..00000000 --- a/src/lombok/installer/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 the lombok installer. It explains to any user that double-clicks the lombok.jar what - * lombok is about, and has the ability to instrument (or remove existing Lombok instrumentation) from any - * Eclipse installation. This package also contains the graphics uses in the installer in SVG format. - */ -package lombok.installer; diff --git a/src/lombok/javac/HandlerLibrary.java b/src/lombok/javac/HandlerLibrary.java deleted file mode 100644 index bbe9dec0..00000000 --- a/src/lombok/javac/HandlerLibrary.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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> annotationHandlers = new HashMap>(); - private final Collection visitorHandlers = new ArrayList(); - 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 { - private JavacAnnotationHandler handler; - private Class annotationClass; - - AnnotationHandlerContainer(JavacAnnotationHandler handler, Class 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 it = ServiceLoader.load(JavacAnnotationHandler.class, - JavacAnnotationHandler.class.getClassLoader()).iterator(); - while (it.hasNext()) { - try { - JavacAnnotationHandler handler = it.next(); - Class annotationClass = - SpiLoadUtil.findAnnotationClass(handler.getClass(), JavacAnnotationHandler.class); - AnnotationHandlerContainer container = new AnnotationHandlerContainer(handler, annotationClass); - if (lib.annotationHandlers.put(container.annotationClass.getName(), container) != null) { - 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 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/lombok/javac/Javac.java b/src/lombok/javac/Javac.java deleted file mode 100644 index 58a24207..00000000 --- a/src/lombok/javac/Javac.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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 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 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 AnnotationValues createAnnotation(Class type, final JavacNode node) { - Map values = new HashMap(); - JCAnnotation anno = (JCAnnotation) node.get(); - List arguments = anno.getArguments(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); - List raws = new ArrayList(); - List guesses = new ArrayList(); - final List positions = new ArrayList(); - 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 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(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/lombok/javac/JavacAST.java b/src/lombok/javac/JavacAST.java deleted file mode 100644 index f2c83fb8..00000000 --- a/src/lombok/javac/JavacAST.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * 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 { - 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 getImportStatements() { - List imports = new ArrayList(); - 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(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 childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - - 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 childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - 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 childNodes = new ArrayList(); - 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> getStatementTypes() { - Collection> collection = new ArrayList>(2); - collection.add(JCStatement.class); - collection.add(JCExpression.class); - return collection; - } - - private static void addIfNotNull(Collection 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> 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> 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> reducedChain = new ArrayList>(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.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/lombok/javac/JavacASTAdapter.java b/src/lombok/javac/JavacASTAdapter.java deleted file mode 100644 index 41bc46d3..00000000 --- a/src/lombok/javac/JavacASTAdapter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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/lombok/javac/JavacASTVisitor.java b/src/lombok/javac/JavacASTVisitor.java deleted file mode 100644 index 3c5887a7..00000000 --- a/src/lombok/javac/JavacASTVisitor.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 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("", LombokNode.getFileName()); - indent++; - } - - @Override public void endVisitCompilationUnit(JavacNode node, JCCompilationUnit unit) { - indent--; - print(""); - } - - @Override public void visitType(JavacNode node, JCClassDecl type) { - print("", type.name); - indent++; - if (printContent) { - print("%s", type); - disablePrinting++; - } - } - - @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode node, JCAnnotation annotation) { - forcePrint("", annotation); - } - - @Override public void endVisitType(JavacNode node, JCClassDecl type) { - if (printContent) disablePrinting--; - indent--; - print("", 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("", initializer.isStatic() ? "static" : "instance"); - } - - @Override public void visitField(JavacNode node, JCVariableDecl field) { - print("", 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); - } - - @Override public void endVisitField(JavacNode node, JCVariableDecl field) { - if (printContent) disablePrinting--; - indent--; - print("", field.vartype, field.name); - } - - @Override public void visitMethod(JavacNode node, JCMethodDecl method) { - final String type; - if (method.name.contentEquals("")) { - 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); - } - - @Override public void endVisitMethod(JavacNode node, JCMethodDecl method) { - if (printContent) disablePrinting--; - indent--; - print("", "XMETHOD", method.name); - } - - @Override public void visitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) { - print("", arg.vartype, arg.name); - indent++; - } - - @Override public void visitAnnotationOnMethodArgument(JCVariableDecl arg, JCMethodDecl method, JavacNode nodeAnnotation, JCAnnotation annotation) { - forcePrint("", annotation); - } - - @Override public void endVisitMethodArgument(JavacNode node, JCVariableDecl arg, JCMethodDecl method) { - indent--; - print("", arg.vartype, arg.name); - } - - @Override public void visitLocal(JavacNode node, JCVariableDecl local) { - print("", local.vartype, local.name); - indent++; - } - - @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode node, JCAnnotation annotation) { - print("", annotation); - } - - @Override public void endVisitLocal(JavacNode node, JCVariableDecl local) { - indent--; - print("", 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("", statement.getClass()); - } - } -} diff --git a/src/lombok/javac/JavacAnnotationHandler.java b/src/lombok/javac/JavacAnnotationHandler.java deleted file mode 100644 index 5b6fe4ce..00000000 --- a/src/lombok/javac/JavacAnnotationHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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} - * - * 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 { - /** - * 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 annotation, JCAnnotation ast, JavacNode annotationNode); -} diff --git a/src/lombok/javac/JavacNode.java b/src/lombok/javac/JavacNode.java deleted file mode 100644 index a0ee2789..00000000 --- a/src/lombok/javac/JavacNode.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 { - /** - * Passes through to the parent constructor. - */ - public JavacNode(JavacAST ast, JCTree node, List 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/lombok/javac/apt/Processor.java b/src/lombok/javac/apt/Processor.java deleted file mode 100644 index 9c851762..00000000 --- a/src/lombok/javac/apt/Processor.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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 annotations, RoundEnvironment roundEnv) { - if (processingEnv == null) { - if (errorToShow != null) { - Set rootElements = roundEnv.getRootElements(); - if (!rootElements.isEmpty()) { - rawProcessingEnv.getMessager().printMessage(Kind.WARNING, errorToShow, rootElements.iterator().next()); - errorToShow = null; - } - } - return false; - } - - IdentityHashMap units = new IdentityHashMap(); - for (Element element : roundEnv.getRootElements()) { - JCCompilationUnit unit = toUnit(element); - if (unit != null) units.put(unit, null); - } - - List asts = new ArrayList(); - - 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/lombok/javac/apt/package-info.java b/src/lombok/javac/apt/package-info.java deleted file mode 100644 index 0c47c40f..00000000 --- a/src/lombok/javac/apt/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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/lombok/javac/handlers/HandleCleanup.java b/src/lombok/javac/handlers/HandleCleanup.java deleted file mode 100644 index 88a8e1d7..00000000 --- a/src/lombok/javac/handlers/HandleCleanup.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 { - @Override public boolean handle(AnnotationValues 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 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 tryBlock = List.nil(); - List 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 finalizerBlock = List.of(maker.Exec( - maker.Apply(List.nil(), cleanupCall, List.nil()))); - - JCBlock finalizer = maker.Block(0, finalizerBlock); - newStatements = newStatements.append(maker.Try(maker.Block(0, tryBlock), List.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 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/lombok/javac/handlers/HandleData.java b/src/lombok/javac/handlers/HandleData.java deleted file mode 100644 index eef7f78d..00000000 --- a/src/lombok/javac/handlers/HandleData.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 { - @Override public boolean handle(AnnotationValues 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 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 fields) { - TreeMaker maker = typeNode.getTreeMaker(); - JCClassDecl type = (JCClassDecl) typeNode.get(); - - List nullChecks = List.nil(); - List assigns = List.nil(); - List params = List.nil(); - - for (JavacNode fieldNode : fields) { - JCVariableDecl field = (JCVariableDecl) fieldNode.get(); - List nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN); - List 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(""), - null, type.typarams, params, List.nil(), maker.Block(0L, nullChecks.appendList(assigns)), null); - } - - private JCMethodDecl createStaticConstructor(String name, JavacNode typeNode, List fields) { - TreeMaker maker = typeNode.getTreeMaker(); - JCClassDecl type = (JCClassDecl) typeNode.get(); - - JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PUBLIC); - - JCExpression returnType, constructorType; - - List typeParams = List.nil(); - List params = List.nil(); - List typeArgs1 = List.nil(); - List typeArgs2 = List.nil(); - List 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 tArgs = List.nil(); - for (JCExpression arg : typeApply.arguments) tArgs = tArgs.append(arg); - pType = maker.TypeApply(typeApply.clazz, tArgs); - } else { - pType = field.vartype; - } - List nonNulls = findAnnotations(fieldNode, TransformationsUtil.NON_NULL_PATTERN); - List 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.nil(), constructorType, args, null)); - JCBlock body = maker.Block(0, List.of(returnStatement)); - - return maker.MethodDef(mods, typeNode.toName(name), returnType, typeParams, params, List.nil(), body, null); - } -} diff --git a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/lombok/javac/handlers/HandleEqualsAndHashCode.java deleted file mode 100644 index 61a4ef63..00000000 --- a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * 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 { - private void checkForBogusFieldNames(JavacNode type, AnnotationValues 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 annotation, JCAnnotation ast, JavacNode annotationNode) { - EqualsAndHashCode ann = annotation.getInstance(); - List excludes = List.from(ann.exclude()); - List 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 excludes, List 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 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 fields, boolean callSuper) { - TreeMaker maker = typeNode.getTreeMaker(); - - JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.nil()); - JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); - JCExpression returnType = maker.TypeIdent(TypeTags.INT); - List 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 intoResult = List.nil(); - - if (callSuper) { - JCMethodInvocation callToSuper = maker.Apply(List.nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), - List.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.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.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.nil(), hcMethod, List.of(thisDotField))); - } else /* objects */ { - /* this.fieldName == null ? 0 : this.fieldName.hashCode() */ - JCExpression hcCall = maker.Apply(List.nil(), maker.Select(thisDotField, typeNode.toName("hashCode")), - List.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.nil(), List.nil(), List.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 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.nil()); - JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); - JCExpression objectType = maker.Type(typeNode.getSymbolTable().objectType); - JCExpression returnType = maker.TypeIdent(TypeTags.BOOLEAN); - - List statements = List.nil(); - List 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 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.nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), - List.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 wildcards1 = List.nil(); - List 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 args = List.of(thisDotField, otherDotField); - statements = statements.append(maker.If(maker.Unary(JCTree.NOT, - maker.Apply(List.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.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.nil(), params, List.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 args = List.of(thisDotField, otherDotField); - JCBinary compareCallEquals0 = maker.Binary(JCTree.NE, maker.Apply( - List.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/lombok/javac/handlers/HandleGetter.java b/src/lombok/javac/handlers/HandleGetter.java deleted file mode 100644 index e60e426d..00000000 --- a/src/lombok/javac/handlers/HandleGetter.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 { - /** - * 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 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 methodGenericParams = List.nil(); - List parameters = List.nil(); - List throwsClauses = List.nil(); - JCExpression annotationMethodDefaultValue = null; - - List nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); - List 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/lombok/javac/handlers/HandlePrintAST.java b/src/lombok/javac/handlers/HandlePrintAST.java deleted file mode 100644 index 4c25694b..00000000 --- a/src/lombok/javac/handlers/HandlePrintAST.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { - @Override public boolean handle(AnnotationValues 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/lombok/javac/handlers/HandleSetter.java b/src/lombok/javac/handlers/HandleSetter.java deleted file mode 100644 index 84032e9c..00000000 --- a/src/lombok/javac/handlers/HandleSetter.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 { - /** - * 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 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 statements; - List nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); - List nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN); - - if (nonNulls.isEmpty()) { - statements = List.of(treeMaker.Exec(assign)); - } else { - JCStatement nullCheck = generateNullCheck(treeMaker, field); - if (nullCheck != null) statements = List.of(nullCheck, treeMaker.Exec(assign)); - else statements = List.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 methodGenericParams = List.nil(); - List parameters = List.of(param); - List throwsClauses = List.nil(); - JCExpression annotationMethodDefaultValue = null; - - return treeMaker.MethodDef(treeMaker.Modifiers(access, List.nil()), methodName, methodType, - methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); - } -} diff --git a/src/lombok/javac/handlers/HandleSneakyThrows.java b/src/lombok/javac/handlers/HandleSneakyThrows.java deleted file mode 100644 index e7879dd1..00000000 --- a/src/lombok/javac/handlers/HandleSneakyThrows.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { - @Override public boolean handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { - Collection exceptionNames = annotation.getRawExpressions("value"); - - List memberValuePairs = ast.getArguments(); - if (memberValuePairs == null || memberValuePairs.size() == 0) return false; - - java.util.List exceptions = new ArrayList(); - 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 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 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 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.of(maker.Throw(maker.Apply( - List.nil(), lombokLombokSneakyThrowNameRef, - List.of(maker.Ident(node.toName("$ex"))))))); - - return maker.Try(tryBlock, List.of(maker.Catch(catchParam, catchBody)), null); - } -} diff --git a/src/lombok/javac/handlers/HandleSynchronized.java b/src/lombok/javac/handlers/HandleSynchronized.java deleted file mode 100644 index c86d99c6..00000000 --- a/src/lombok/javac/handlers/HandleSynchronized.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { - private static final String INSTANCE_LOCK_NAME = "$lock"; - private static final String STATIC_LOCK_NAME = "$LOCK"; - - @Override public boolean handle(AnnotationValues 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.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.of(maker.Synchronized(lockNode, method.body))); - - methodNode.rebuild(); - - return true; - } -} diff --git a/src/lombok/javac/handlers/HandleToString.java b/src/lombok/javac/handlers/HandleToString.java deleted file mode 100644 index f7251ab8..00000000 --- a/src/lombok/javac/handlers/HandleToString.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 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 { - private void checkForBogusFieldNames(JavacNode type, AnnotationValues 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 annotation, JCAnnotation ast, JavacNode annotationNode) { - ToString ann = annotation.getInstance(); - List excludes = List.from(ann.exclude()); - List 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 excludes, List 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 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 fields, boolean includeFieldNames, boolean callSuper) { - TreeMaker maker = typeNode.getTreeMaker(); - - JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.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.nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("toString")), - List.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.nil(), hcMethod, List.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.nil(), List.nil(), List.nil(), body, null); - } - -} diff --git a/src/lombok/javac/handlers/JavacHandlerUtil.java b/src/lombok/javac/handlers/JavacHandlerUtil.java deleted file mode 100644 index 34d8b849..00000000 --- a/src/lombok/javac/handlers/JavacHandlerUtil.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * 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 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("")) { - 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("")) { - //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 addAllButOne(List defs, int idx) { - List 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 findAnnotations(JavacNode fieldNode, Pattern namePattern) { - List 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.nil(), npe, List.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 createListOfNonExistentFields(List 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 problematic = List.nil(); - for (int i = 0 ; i < list.size() ; i++) { - if (!matched[i]) problematic = problematic.append(i); - } - - return problematic; - } -} diff --git a/src/lombok/javac/handlers/package-info.java b/src/lombok/javac/handlers/package-info.java deleted file mode 100644 index b08d6af3..00000000 --- a/src/lombok/javac/handlers/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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/lombok/javac/package-info.java b/src/lombok/javac/package-info.java deleted file mode 100644 index 0df2f050..00000000 --- a/src/lombok/javac/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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/lombok/package-info.java b/src/lombok/package-info.java deleted file mode 100644 index 6d5af3d1..00000000 --- a/src/lombok/package-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; -- cgit