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. --- .classpath | 5 +- build.xml | 2 +- buildScripts/compile.ant.xml | 34 +- buildScripts/maven.ant.xml | 6 +- buildScripts/website.ant.xml | 2 +- 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 - .../lombok/eclipse/agent/EclipsePatcher.java | 215 ----- .../lombok/eclipse/agent/PatchFixes.java | 67 -- .../lombok/eclipse/agent/package-info.java | 26 - 165 files changed, 12936 insertions(+), 12915 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 delete mode 100644 src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java delete mode 100644 src_eclipseagent/lombok/eclipse/agent/PatchFixes.java delete mode 100644 src_eclipseagent/lombok/eclipse/agent/package-info.java diff --git a/.classpath b/.classpath index 185f8a64..b59d0baa 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,9 @@ - + + + - diff --git a/build.xml b/build.xml index 8d45154f..a2571d54 100644 --- a/build.xml +++ b/build.xml @@ -31,7 +31,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - + - - + + + + + + + + + - - + + + - + + + + + - - - - + + + + + + + + diff --git a/buildScripts/maven.ant.xml b/buildScripts/maven.ant.xml index 43fed149..f2495bcd 100644 --- a/buildScripts/maven.ant.xml +++ b/buildScripts/maven.ant.xml @@ -78,7 +78,11 @@ the maven repository that contains lombok's deliverables for those using maven. ${mvn.bin.md5} ${mvn.bin.sha1} - + + + + + ${mvn.src.md5} diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index c8bdf2c2..30d0b29b 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -230,7 +230,7 @@ such as converting the changelog into HTML, and creating javadoc. check the lombok package. If you're trying to extend lombok or write your own plugins, the other packages are what you're looking for. ]]> - + 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; diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java b/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java deleted file mode 100644 index 7d2a28bc..00000000 --- a/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java +++ /dev/null @@ -1,215 +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.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 deleted file mode 100644 index 5d54692e..00000000 --- a/src_eclipseagent/lombok/eclipse/agent/PatchFixes.java +++ /dev/null @@ -1,67 +0,0 @@ -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 deleted file mode 100644 index 12255f81..00000000 --- a/src_eclipseagent/lombok/eclipse/agent/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 eclipse by being loaded as a javaagent. - */ -package lombok.eclipse.agent; -- cgit