From 893b5ea69b6c584d924c5d7fb5b1a6e5355295c4 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 21:01:53 +0100 Subject: startOnFirstThread is no longer necessary on modern JVMs on mac. --- build.xml | 7 ------- buildScripts/eclipse-debug-target.template | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/build.xml b/build.xml index 99960bd2..84e77883 100644 --- a/build.xml +++ b/build.xml @@ -274,18 +274,11 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - - - - - - - diff --git a/buildScripts/eclipse-debug-target.template b/buildScripts/eclipse-debug-target.template index 033f8197..bb45cd69 100644 --- a/buildScripts/eclipse-debug-target.template +++ b/buildScripts/eclipse-debug-target.template @@ -21,7 +21,7 @@ - + -- cgit From 653eb56883a6be109ee7e767fae920cae70f569d Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 22:06:09 +0100 Subject: changed the pattern for writing dependencies to the various lib/ directories to be organization-name.jar instead of just name.jar, in order to account for the ever lovely and wonderful apache's crazy decision to call the entirely separate log4j v2.0 also 'log4j'. This does mean you'll have to 'ant clean'. --- build.xml | 8 ++++---- test/core/src/lombok/RunTestsViaEcj.java | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build.xml b/build.xml index 84e77883..9de92d59 100644 --- a/build.xml +++ b/build.xml @@ -27,7 +27,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - + @@ -196,7 +196,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - + @@ -271,7 +271,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - + - + diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index 0bf97213..ca443620 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -129,10 +129,11 @@ public class RunTestsViaEcj extends AbstractRunTests { } } classpath.add("dist/lombok.jar"); - classpath.add("lib/test/commons-logging.jar"); - classpath.add("lib/test/slf4j-api.jar"); - classpath.add("lib/test/slf4j-ext.jar"); - classpath.add("lib/test/log4j.jar"); + classpath.add("lib/test/commons-logging-commons-logging.jar"); + classpath.add("lib/test/org.slf4j-slf4j-api.jar"); + classpath.add("lib/test/org.slf4j-slf4j-ext.jar"); + classpath.add("lib/test/log4j-log4j.jar"); + classpath.add("lib/test/org.apache.logging.log4j-log4j-api.jar"); return new FileSystem(classpath.toArray(new String[0]), new String[] {file.getAbsolutePath()}, "UTF-8"); } } -- cgit From 56150952c451f0d8c2018424191d4480ac5e8460 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 22:04:27 +0100 Subject: Added @Log4j2 support. --- buildScripts/ivy.xml | 1 + doc/changelog.markdown | 1 + src/core/lombok/eclipse/handlers/HandleLog.java | 15 +++++- .../lombok/extern/apachecommons/CommonsLog.java | 3 +- src/core/lombok/extern/java/Log.java | 3 +- src/core/lombok/extern/log4j/Log4j.java | 3 +- src/core/lombok/extern/log4j/Log4j2.java | 62 ++++++++++++++++++++++ src/core/lombok/extern/slf4j/Slf4j.java | 3 +- src/core/lombok/extern/slf4j/XSlf4j.java | 3 +- src/core/lombok/javac/handlers/HandleLog.java | 17 +++++- .../resource/after-delombok/LoggerCommons.java | 1 - .../resource/after-delombok/LoggerJul.java | 1 - .../resource/after-delombok/LoggerLog4j.java | 1 - .../resource/after-delombok/LoggerLog4j2.java | 6 +++ .../resource/after-delombok/LoggerSlf4j.java | 2 - .../resource/after-delombok/LoggerXSlf4j.java | 1 - .../transform/resource/after-ecj/LoggerLog4j2.java | 17 ++++++ test/transform/resource/before/LoggerLog4j2.java | 9 ++++ website/features/Log.html | 6 ++- 19 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 src/core/lombok/extern/log4j/Log4j2.java create mode 100644 test/transform/resource/after-delombok/LoggerLog4j2.java create mode 100644 test/transform/resource/after-ecj/LoggerLog4j2.java create mode 100644 test/transform/resource/before/LoggerLog4j2.java diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index b1440db5..fd6c503a 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -17,6 +17,7 @@ + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index a415cd01..3b56aa00 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -4,6 +4,7 @@ Lombok Changelog ### v0.11.7 (Edgy Guinea Pig) * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) +* FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index bffe2d62..2e7b4475 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -177,6 +177,16 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleLog4j2Log extends EclipseAnnotationHandler { + @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode); + } + } + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for Eclipse. */ @@ -224,6 +234,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"), diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index f178ae05..024e3744 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -52,6 +52,7 @@ import java.lang.annotation.Target; * @see org.apache.commons.logging.LogFactory#getLog(java.lang.Class) org.apache.commons.logging.LogFactory.getLog(Class target) * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index 90c62956..7ae4e07b 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see java.util.logging.Logger#getLogger(java.lang.String) java.util.logging.Logger.getLogger(String name) * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 9cfc5839..29e1b27c 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -50,6 +50,7 @@ import java.lang.annotation.Target; * * @see org.apache.log4j.Logger org.apache.log4j.Logger * @see org.apache.log4j.Logger#getLogger(java.lang.Class) org.apache.log4j.Logger.getLogger(Class target) + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.slf4j.Slf4j @Slf4j diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java new file mode 100644 index 00000000..2a0f09e1 --- /dev/null +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.log4j; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + *

+ * Complete documentation is found at the project lombok features page for lombok log annotations. + *

+ * Example: + *

+ * @Log4j2
+ * public class LogExample {
+ * }
+ * 
+ * + * will generate: + * + *
+ * public class LogExample {
+ *     private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.Logger.getLogger(LogExample.class);
+ * }
+ * 
+ * + * This annotation is valid for classes and enumerations.
+ * + * @see org.apache.logging.log4j.Logger org.apache.logging.log4j.Logger + * @see org.apache.logging.log4j.LogManager#getLogger(java.lang.Class) org.apache.logging.log4j.LogManager.getLogger(Class target) + * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.apachecommons.CommonsLog @CommonsLog + * @see lombok.extern.java.Log @Log + * @see lombok.extern.slf4j.Slf4j @Slf4j + * @see lombok.extern.slf4j.XSlf4j @XSlf4j + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log4j2 { +} \ No newline at end of file diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index 14dbcba6..45942971 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index bdf8a62c..599c68ab 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Project Lombok Authors. + * Copyright (C) 2012-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j */ @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 62a55c44..35a32be5 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -82,7 +82,7 @@ public class HandleLog { private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); - // private static final log = (); + // private static final log = (); JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName()); JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName()); @@ -127,6 +127,16 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleLog4j2Log extends JavacAnnotationHandler { + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, annotationNode); + } + } + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for javac. */ @@ -163,6 +173,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J(lombok.extern.log4j.Log4j.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2(lombok.extern.log4j.Log4j2.class, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager.getLogger"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J(lombok.extern.slf4j.Slf4j.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"), diff --git a/test/transform/resource/after-delombok/LoggerCommons.java b/test/transform/resource/after-delombok/LoggerCommons.java index c2a03815..dfe3e88d 100644 --- a/test/transform/resource/after-delombok/LoggerCommons.java +++ b/test/transform/resource/after-delombok/LoggerCommons.java @@ -1,7 +1,6 @@ class LoggerCommons { private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LoggerCommons.class); } - class LoggerCommonsWithImport { private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LoggerCommonsWithImport.class); } \ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerJul.java b/test/transform/resource/after-delombok/LoggerJul.java index 39cb2aac..b020c540 100644 --- a/test/transform/resource/after-delombok/LoggerJul.java +++ b/test/transform/resource/after-delombok/LoggerJul.java @@ -1,7 +1,6 @@ class LoggerJul { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggerJul.class.getName()); } - class LoggerJulWithImport { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggerJulWithImport.class.getName()); } \ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerLog4j.java b/test/transform/resource/after-delombok/LoggerLog4j.java index 6892a7d8..dfbad89a 100644 --- a/test/transform/resource/after-delombok/LoggerLog4j.java +++ b/test/transform/resource/after-delombok/LoggerLog4j.java @@ -1,7 +1,6 @@ class LoggerLog4j { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LoggerLog4j.class); } - class LoggerLog4jWithImport { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LoggerLog4jWithImport.class); } \ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerLog4j2.java b/test/transform/resource/after-delombok/LoggerLog4j2.java new file mode 100644 index 00000000..3447a9a5 --- /dev/null +++ b/test/transform/resource/after-delombok/LoggerLog4j2.java @@ -0,0 +1,6 @@ +class LoggerLog4j2 { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2.class); +} +class LoggerLog4j2WithImport { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2WithImport.class); +} \ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerSlf4j.java b/test/transform/resource/after-delombok/LoggerSlf4j.java index cb1486ba..4cc7c107 100644 --- a/test/transform/resource/after-delombok/LoggerSlf4j.java +++ b/test/transform/resource/after-delombok/LoggerSlf4j.java @@ -1,11 +1,9 @@ class LoggerSlf4j { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggerSlf4j.class); } - class LoggerSlf4jWithImport { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggerSlf4jWithImport.class); } - class LoggerSlf4jOuter { static class Inner { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Inner.class); diff --git a/test/transform/resource/after-delombok/LoggerXSlf4j.java b/test/transform/resource/after-delombok/LoggerXSlf4j.java index 0239c60b..7d8f3236 100644 --- a/test/transform/resource/after-delombok/LoggerXSlf4j.java +++ b/test/transform/resource/after-delombok/LoggerXSlf4j.java @@ -1,7 +1,6 @@ class LoggerXSlf4j { private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LoggerXSlf4j.class); } - class LoggerXSlf4jWithImport { private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LoggerXSlf4jWithImport.class); } \ No newline at end of file diff --git a/test/transform/resource/after-ecj/LoggerLog4j2.java b/test/transform/resource/after-ecj/LoggerLog4j2.java new file mode 100644 index 00000000..c1368e5d --- /dev/null +++ b/test/transform/resource/after-ecj/LoggerLog4j2.java @@ -0,0 +1,17 @@ +import lombok.extern.log4j.Log4j2; +@lombok.extern.log4j.Log4j2 class LoggerLog4j2 { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2.class); + () { + } + LoggerLog4j2() { + super(); + } +} +@Log4j2 class LoggerLog4j2WithImport { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2WithImport.class); + () { + } + LoggerLog4j2WithImport() { + super(); + } +} \ No newline at end of file diff --git a/test/transform/resource/before/LoggerLog4j2.java b/test/transform/resource/before/LoggerLog4j2.java new file mode 100644 index 00000000..b7ea99ee --- /dev/null +++ b/test/transform/resource/before/LoggerLog4j2.java @@ -0,0 +1,9 @@ +import lombok.extern.log4j.Log4j2; + +@lombok.extern.log4j.Log4j2 +class LoggerLog4j2 { +} + +@Log4j2 +class LoggerLog4j2WithImport { +} \ No newline at end of file diff --git a/website/features/Log.html b/website/features/Log.html index fb529426..2fb91956 100644 --- a/website/features/Log.html +++ b/website/features/Log.html @@ -16,9 +16,9 @@

Overview

NEW in lombok 0.10: You can annotate any class with a log annotation to let lombok generate a logger field.
- The logger is named log and field's type depends on which logger you have selected. + The logger is named log and the field's type depends on which logger you have selected.

- There are four choices available:
+ There are six choices available:

@CommonsLog
Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@@ -26,6 +26,8 @@
Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
+
@Log4j2
+
Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
-- cgit From ce73a67803a357f45d1e000d4dc3d1f5004d204a Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 23:21:22 +0100 Subject: Added installer support for JBoss Developer Studio which is an eclipse variant. Also fixed a bug where eclipses installed in user.home weren't found automatically on windows. Also fixed a bug where STS installed in C:\Program Files (X86) wasn't found automatically. --- .../lombok/installer/eclipse/EclipseFinder.java | 22 ++++--- .../lombok/installer/eclipse/JbdsFinder.java | 70 +++++++++++++++++++++ .../lombok/installer/eclipse/JbdsLocation.java | 45 +++++++++++++ .../installer/eclipse/JbdsLocationProvider.java | 69 ++++++++++++++++++++ .../lombok/installer/eclipse/STSFinder.java | 2 +- src/installer/lombok/installer/eclipse/jbds.png | Bin 0 -> 3470 bytes 6 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 src/installer/lombok/installer/eclipse/JbdsFinder.java create mode 100644 src/installer/lombok/installer/eclipse/JbdsLocation.java create mode 100644 src/installer/lombok/installer/eclipse/JbdsLocationProvider.java create mode 100644 src/installer/lombok/installer/eclipse/jbds.png diff --git a/src/installer/lombok/installer/eclipse/EclipseFinder.java b/src/installer/lombok/installer/eclipse/EclipseFinder.java index 9fbcabbc..8a1a689a 100644 --- a/src/installer/lombok/installer/eclipse/EclipseFinder.java +++ b/src/installer/lombok/installer/eclipse/EclipseFinder.java @@ -64,14 +64,10 @@ public class EclipseFinder extends IdeFinder { /** * 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. + * The search process works by scanning for each 'source dir' for either an eclipse installation or a folder containing the text returned + * by getDirName(). If such a folder is found, this process is applied recursively. On windows, this process is run on each drive letter + * which represents a physical hard disk. If the native windows API call to determine these drive letters fails, only 'C:' is checked. */ private List getSourceDirsOnWindowsWithDriveLetters() { List driveLetters = asList("C"); @@ -83,12 +79,22 @@ public class EclipseFinder extends IdeFinder { List sourceDirs = new ArrayList(); for (String letter : driveLetters) { for (String possibleSource : getSourceDirsOnWindows()) { - sourceDirs.add(letter + ":" + possibleSource); + if (!isDriveSpecificOnWindows(possibleSource)) { + sourceDirs.add(letter + ":" + possibleSource); + } } } + for (String possibleSource : getSourceDirsOnWindows()) { + if (isDriveSpecificOnWindows(possibleSource)) sourceDirs.add(possibleSource); + } + return sourceDirs; } + public boolean isDriveSpecificOnWindows(String path) { + return path.length() > 1 && path.charAt(1) == ':'; + } + protected List getSourceDirsOnMac() { return Arrays.asList("/Applications", System.getProperty("user.home", ".")); } diff --git a/src/installer/lombok/installer/eclipse/JbdsFinder.java b/src/installer/lombok/installer/eclipse/JbdsFinder.java new file mode 100644 index 00000000..2dfaacba --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsFinder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.eclipse; + +import java.util.Arrays; +import java.util.List; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; + +import org.mangosdk.spi.ProviderFor; + +/** + * JBDS (JBoss Developer Studio) is an eclipse variant. + * Other than different executable names, it's the same as eclipse, as far as lombok support goes. + */ +@ProviderFor(IdeFinder.class) +public class JbdsFinder extends EclipseFinder { + @Override protected IdeLocation createLocation(String guess) throws CorruptedIdeLocationException { + return new JbdsLocationProvider().create0(guess); + } + + @Override protected String getDirName() { + return "studio"; + } + + @Override protected String getMacExecutableName() { + return "jbdevstudio.app"; + } + + @Override protected String getUnixExecutableName() { + return "jbdevstudio"; + } + + @Override protected String getWindowsExecutableName() { + return "jbdevstudio.exe"; + } + + @Override protected List getSourceDirsOnWindows() { + return Arrays.asList("\\", "\\Program Files", "\\Program Files (x86)", System.getProperty("user.home", ".")); + } + + @Override protected List getSourceDirsOnMac() { + return Arrays.asList("/Applications", System.getProperty("user.home", ".")); + } + + @Override protected List getSourceDirsOnUnix() { + return Arrays.asList(System.getProperty("user.home", ".")); + } +} diff --git a/src/installer/lombok/installer/eclipse/JbdsLocation.java b/src/installer/lombok/installer/eclipse/JbdsLocation.java new file mode 100644 index 00000000..81fb5261 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsLocation.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.eclipse; + +import java.io.File; +import java.net.URL; + +import lombok.installer.CorruptedIdeLocationException; + +public class JbdsLocation extends EclipseLocation { + public JbdsLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { + super(nameOfLocation, pathToEclipseIni); + } + + @Override public URL getIdeIcon() { + return JbdsLocation.class.getResource("jbds.png"); + } + + @Override protected String getIniFileName() { + return "jbdevstudio.ini"; + } + + @Override protected String getTypeName() { + return "JBoss Developer Studio"; + } +} diff --git a/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java new file mode 100644 index 00000000..e6df0e43 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.eclipse; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeLocation; +import lombok.installer.IdeLocationProvider; +import lombok.installer.IdeFinder.OS; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class JbdsLocationProvider extends EclipseLocationProvider { + @Override protected List getEclipseExecutableNames() { + return Arrays.asList("jbdevstudio.app", "jbdevstudio.exe", "jbdevstudioc.exe", "jbdevstudio"); + } + + @Override protected String getIniName() { + return "jbdevstudio.ini"; + } + + @Override protected IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { + return new JbdsLocation(name, ini); + } + + @Override protected String getMacAppName() { + return "jbdevstudio.app"; + } + + @Override protected String getUnixAppName() { + return "jbdevstudio"; + } + + @Override public Pattern getLocationSelectors(OS os) { + switch (os) { + case MAC_OS_X: + return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini|jbdevstudio\\.app)$", Pattern.CASE_INSENSITIVE); + case WINDOWS: + return Pattern.compile("^(jbdevstudioc?\\.exe|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); + default: + case UNIX: + return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); + } + } +} diff --git a/src/installer/lombok/installer/eclipse/STSFinder.java b/src/installer/lombok/installer/eclipse/STSFinder.java index 74b8ed34..82bc9b80 100644 --- a/src/installer/lombok/installer/eclipse/STSFinder.java +++ b/src/installer/lombok/installer/eclipse/STSFinder.java @@ -57,7 +57,7 @@ public class STSFinder extends EclipseFinder { } @Override protected List getSourceDirsOnWindows() { - return Arrays.asList("\\", "\\springsource", "\\Program Files", "\\Program Files\\springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "\\springsource"); + return Arrays.asList("\\", "\\springsource", "\\Program Files", "\\Program Files (x86)", "\\Program Files\\springsource", "\\Program Files (x86)\\springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "\\springsource"); } @Override protected List getSourceDirsOnMac() { diff --git a/src/installer/lombok/installer/eclipse/jbds.png b/src/installer/lombok/installer/eclipse/jbds.png new file mode 100644 index 00000000..ca7738e6 Binary files /dev/null and b/src/installer/lombok/installer/eclipse/jbds.png differ -- cgit From c0daffdb438e1ded5f96d3425b23067c66042a1c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 23:30:20 +0100 Subject: Updated documentation to reflect support of JBoss Developer Studio --- doc/changelog.markdown | 1 + website/download.html | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 3b56aa00..401349db 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -5,6 +5,7 @@ Lombok Changelog * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) +* ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/website/download.html b/website/download.html index bb354c8f..dea43799 100644 --- a/website/download.html +++ b/website/download.html @@ -44,7 +44,7 @@ - + @@ -53,10 +53,10 @@ - - + + - + @@ -68,7 +68,7 @@ - + -- cgit From 9400f39d12813740634bba233aacc6edcf62c584 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 12 Mar 2013 00:25:50 +0100 Subject: Fix for issue #436: In eclipse, calling i.e. `list.get(i).extensionMethod()` would fail with VerifyError when that code is executed in eclipse. --- doc/changelog.markdown | 1 + src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 401349db..a3ac5ea4 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -6,6 +6,7 @@ Lombok Changelog * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) * ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) +* BUGFIX: `@ExtensionMethods` no longer causes `VerifyError` exceptions when running eclipse-compiled code if extension methods are called on expressions which are method calls whose return type is a type variable. For example, `someList.get(i).extensionMethod()` would fail that way. [Issue #436](http://code.google.com/p/projectlombok/issues/detail?id=436) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index 008e722a..a3d77055 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java @@ -236,10 +236,8 @@ public class PatchExtensionMethod { for (int i = 0, iend = arguments.size(); i < iend; i++) { Expression arg = arguments.get(i); if (fixedBinding.parameters[i].isArrayType() != arg.resolvedType.isArrayType()) break; - if (arg.resolvedType.isArrayType()) { - if (arg instanceof MessageSend) { - ((MessageSend) arg).valueCast = arg.resolvedType; - } + if (arg instanceof MessageSend) { + ((MessageSend) arg).valueCast = arg.resolvedType; } if (!fixedBinding.parameters[i].isBaseType() && arg.resolvedType.isBaseType()) { int id = arg.resolvedType.id; -- cgit From cb9b907839aa0c72ffa41f8a13bfab5cb1341258 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 12 Mar 2013 01:29:15 +0100 Subject: In delombok, we mark the AST as changed if we remove an annotation; this fixes the issue where delombok would leave lombok annotations in the file if that annotation had no actual effect (such as @Getter on a field if there is an explicit getX method for that field). issue #443: delombok would screw up @SneakyThrows on methods or constructors with empty bodies. Now we generate warnings for this. --- .../eclipse/handlers/HandleSneakyThrows.java | 24 ++++++++++++++++++---- .../lombok/javac/handlers/HandleSneakyThrows.java | 19 +++++++++++++++-- .../lombok/javac/handlers/JavacHandlerUtil.java | 1 + website/features/SneakyThrows.html | 6 +++++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java index b7c8a5d8..aa78ca3b 100644 --- a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java @@ -40,6 +40,8 @@ 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.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -147,7 +149,21 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler { return; } - if (method.statements == null) return; + if (method.statements == null || method.statements.length == 0) { + boolean hasConstructorCall = false; + if (method instanceof ConstructorDeclaration) { + ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall; + hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis(); + } + + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } + + return; + } Statement[] contents = method.statements; @@ -160,9 +176,9 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler { } private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) { - int methodStart = method.bodyStart; - int methodEnd = method.bodyEnd; - long methodPosEnd = methodEnd << 32 | (methodEnd & 0xFFFFFFFFL); + int methodStart = method.bodyStart; + int methodEnd = method.bodyEnd; + long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL); TryStatement tryStatement = new TryStatement(); setGeneratedBy(tryStatement, source); diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java index a5bd74e7..c2394fc8 100644 --- a/src/core/lombok/javac/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java @@ -84,13 +84,20 @@ public class HandleSneakyThrows extends JavacAnnotationHandler { return; } - if (method.body == null) return; - if (method.body.stats.isEmpty()) return; + if (method.body == null || method.body.stats.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, false); + return; + } final JCStatement constructorCall = method.body.stats.get(0); final boolean isConstructorCall = isConstructorCall(constructorCall); List contents = isConstructorCall ? method.body.stats.tail : method.body.stats; + if (contents == null || contents.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, true); + return; + } + for (String exception : exceptions) { contents = List.of(buildTryCatchBlock(methodNode, contents, exception, annotation.get())); } @@ -99,6 +106,14 @@ public class HandleSneakyThrows extends JavacAnnotationHandler { methodNode.rebuild(); } + private void generateEmptyBlockWarning(JavacNode methodNode, JavacNode annotation, boolean hasConstructorCall) { + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } + } + private boolean isConstructorCall(final JCStatement supect) { if (!(supect instanceof JCExpressionStatement)) return false; final JCExpression supectExpression = ((JCExpressionStatement) supect).expr; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index c2de5b05..e79dd5dc 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -305,6 +305,7 @@ public class JavacHandlerUtil { return; } + parentNode.getAst().setChanged(); deleteImportFromCompilationUnit(annotation, annotationType.getName()); } diff --git a/website/features/SneakyThrows.html b/website/features/SneakyThrows.html index 0f04b7d9..808a7879 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -64,7 +64,11 @@ statement in a try/catch block with just e.printStackTrace() in the catch block. This is so spectacularly non-productive compared to just sneakily throwing the exception onwards, that Roel and Reinier feel more than justified in claiming that the checked exception system is far from perfect, and thus an opt-out mechanism is warranted. -

+

+ If you put @SneakyThrows on a constructor, any call to a sibling or super constructor is excluded from the @SneakyThrows treatment. This is a + java restriction we cannot work around: Calls to sibling/super constructors MUST be the first statement in the constructor; they cannot be placed inside try/catch blocks. +

+ @SneakyThrows on an empty method, or a constructor that is empty or only has a call to a sibling / super constructor results in no try/catch block and a warning.

- + diff --git a/website/index.html b/website/index.html index dcac3ab5..97b51114 100644 --- a/website/index.html +++ b/website/index.html @@ -87,7 +87,7 @@
diff --git a/website/features/Data.html b/website/features/Data.html index 8ace96cb..1c8510b7 100644 --- a/website/features/Data.html +++ b/website/features/Data.html @@ -75,7 +75,7 @@
diff --git a/website/features/Delegate.html b/website/features/Delegate.html index 532f3f54..02cdf290 100644 --- a/website/features/Delegate.html +++ b/website/features/Delegate.html @@ -55,16 +55,15 @@ When passing classes to the annotation's types or excludes parameter, you cannot include generics. This is a limitation of java. Use private inner interfaces or classes that extend the intended type including the generics parameter to work around this problem. -

-

+

When passing classes to the annotation, these classes do not need to be supertypes of the field. See the example. -

-

+

@Delegate cannot be used on static fields or methods. -

+

+
diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index bc5ecb0c..f70c8e78 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -46,7 +46,7 @@
diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index 03704119..c78b03bd 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -75,7 +75,7 @@
diff --git a/website/features/Log.html b/website/features/Log.html index 2fb91956..2d4fa375 100644 --- a/website/features/Log.html +++ b/website/features/Log.html @@ -60,7 +60,7 @@
diff --git a/website/features/NonNull.html b/website/features/NonNull.html new file mode 100644 index 00000000..9faad502 --- /dev/null +++ b/website/features/NonNull.html @@ -0,0 +1,73 @@ + + + + + + + + val +
+
+
+ +

@NonNull

+ +
+

Overview

+

+ NEW in Lombok 0.11.10: You can use @NonNull on the parameter of a method or constructor to have lombok generate a null-check statement for you. +

+ Lombok has always treated any annotation named @NonNull on a field as a signal to generate a null-check if lombok generates an entire method or constructor for you, via + for example @Data. Now, however, using lombok's own @lombok.NonNull on a parameter results in the insertion of just the null-check + statement inside your own method or constructor. +

+ The null-check looks like if (param == null) throw new NullPointerException("param"); and will be inserted at the very top of your method. For constructors, the null-check + will be inserted immediately following any explicit this() or super() calls. +

+ If a null-check is already present at the top, no additional null-check will be generated. +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Lombok's detection scheme for already existing null-checks consists of scanning for if statements that look just like lombok's own. Any 'throws' statement as + the 'then' part of the if statement, whether in braces or not, counts. The conditional of the if statement must look exactly like PARAMNAME == null. + The first statement in your method that is not such a null-check stops the process of inspecting for null-checks. +

+ While @Data and other method-generating lombok annotations will trigger on any annotation named @NonNull regardless of casing or package name, + this feature only triggers on lombok's own @NonNull annotation from the lombok package. +

+ A @NonNull on a primitive parameter results in a warning. No null-check will be generated. +

+
+
+ +
+
+
+ + + diff --git a/website/features/SneakyThrows.html b/website/features/SneakyThrows.html index 808a7879..573bd95c 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -54,8 +54,6 @@

Small print

- @SneakyThrows is an implementation of this feature request: http://bugs.sun.com/view_bug.do?bug_id=6534270. -

Because @SneakyThrows is an implementation detail and not part of your method signature, it is an error if you try to declare a checked exception as sneakily thrown when you don't call any methods that throw this exception. (Doing so is perfectly legal for throws statements to accommodate subclasses). Similarly, @SneakyThrows does not inherit. @@ -72,7 +70,7 @@

diff --git a/website/features/Synchronized.html b/website/features/Synchronized.html index 4b6ef251..9ab6c87f 100644 --- a/website/features/Synchronized.html +++ b/website/features/Synchronized.html @@ -59,7 +59,7 @@
diff --git a/website/features/ToString.html b/website/features/ToString.html index c3b389ba..585dc72b 100644 --- a/website/features/ToString.html +++ b/website/features/ToString.html @@ -68,7 +68,7 @@
diff --git a/website/features/index.html b/website/features/index.html index 9751a37d..8b3765c1 100644 --- a/website/features/index.html +++ b/website/features/index.html @@ -13,10 +13,14 @@

Lombok features

+
val
+
Finally! Hassle-free final local variables.
+
@NonNull
+
or: How I learned to stop worrying and love the NullPointerException.
+
@Cleanup
+
Automatic resource management: Call your close() methods safely with no hassle.
@Getter / @Setter
Never write public int getFoo() {return foo;} again.
-
@Getter(lazy=true)
-
Laziness is a virtue!
@ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!
@EqualsAndHashCode
@@ -26,16 +30,14 @@
@Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
-
@Cleanup
-
Automatic resource management: Call your close() methods safely with no hassle.
-
@Synchronized
-
synchronized done right: Don't expose your locks.
@SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
+
@Synchronized
+
synchronized done right: Don't expose your locks.
+
@Getter(lazy=true)
+
Laziness is a virtue!
@Log
Captain's Log, stardate 24435.7: "What was that line again?"
-
val
-
Finally! Hassle-free final local variables.
@Delegate
Don't lose your composition.
experimental features
diff --git a/website/features/val.html b/website/features/val.html index cec799e9..c4c8ad27 100644 --- a/website/features/val.html +++ b/website/features/val.html @@ -50,7 +50,7 @@
-- cgit From 5fd82596f6e80c71da1012e423419eed5c293ac2 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 13 Jun 2013 00:33:14 +0200 Subject: Added some very limited reporting when OutOfMemorErrors occur during parse tree builder under javac. Let's hope our users reporting these issues can use this to figure out which files are triggering the issue. --- src/core/lombok/javac/JavacAST.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 5a91258c..36c51210 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -315,9 +315,18 @@ public class JavacAST extends AST { } 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)); + try { + 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)); + } catch (OutOfMemoryError oome) { + String msg = oome.getMessage(); + if (msg == null) msg = "(no original message)"; + OutOfMemoryError newError = new OutOfMemoryError(getFileName() + "@pos" + statement.getPreferredPosition() + ": " + msg); + // We could try to set the stack trace of the new exception to the same one as the old exception, but this costs memory, + // and we're already in an extremely fragile situation in regards to remaining heap space, so let's not do that. + throw newError; + } } /** For javac, both JCExpression and JCStatement are considered as valid children types. */ -- cgit From b3dac7722e699b3f18169265fd77f1731a82d960 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 16 Jun 2013 10:57:53 +0200 Subject: issue 536: annotationprocessor now always returns false. --- src/core/lombok/core/AnnotationProcessor.java | 7 ++----- src/core/lombok/javac/apt/Processor.java | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index 8c5f7fba..e9cf3891 100644 --- a/src/core/lombok/core/AnnotationProcessor.java +++ b/src/core/lombok/core/AnnotationProcessor.java @@ -164,12 +164,9 @@ public class AnnotationProcessor extends AbstractProcessor { } } - boolean handled = false; - for (ProcessorDescriptor proc : active) { - handled |= proc.process(annotations, roundEnv); - } + for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv); - return handled; + return false; } /** diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index 96150b06..110acaad 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -62,10 +62,6 @@ import com.sun.tools.javac.util.Context; /** * 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. */ -- cgit From bf43dc747791f9bbf953cfea8200fac478f62d80 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 21:12:03 +0200 Subject: Removed a SuppressWarnings which old eclipse doesn't care about for some reason... now I'm just confused. Do we need it or not? --- src/utils/lombok/core/ImmutableList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/lombok/core/ImmutableList.java b/src/utils/lombok/core/ImmutableList.java index a151ae6f..8b478dbc 100644 --- a/src/utils/lombok/core/ImmutableList.java +++ b/src/utils/lombok/core/ImmutableList.java @@ -57,7 +57,7 @@ public final class ImmutableList implements Iterable { return new ImmutableList(new Object[] {a, b, c, d, e}); } - public static ImmutableList of(T a, T b, T c, T d, T e, T f, @SuppressWarnings("unchecked") T... g) { + public static ImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { Object[] rest = g == null ? new Object[] {null} : g; Object[] val = new Object[rest.length + 6]; System.arraycopy(rest, 0, val, 6, rest.length); -- cgit From 4e521c558ed7355244dd1cb9c8d94bd5a9cb462d Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 12:50:59 +0200 Subject: Added injectType methods to Eclipse/JavacHandlerUtil, which we'll need to inject the created $Builder type. Inspired by Philipp Eichhorn's work in lombok-pg. --- .../lombok/eclipse/handlers/EclipseHandlerUtil.java | 21 +++++++++++++++++++++ .../lombok/javac/handlers/JavacHandlerUtil.java | 13 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index dc99dabf..f9295150 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1289,6 +1289,27 @@ public class EclipseHandlerUtil { type.add(method, Kind.METHOD); } + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + */ + public static void injectType(final EclipseNode typeNode, final TypeDeclaration type) { + type.annotations = createSuppressWarningsAll(type, type.annotations); + TypeDeclaration parent = (TypeDeclaration) typeNode.get(); + + if (parent.memberTypes == null) { + parent.memberTypes = new TypeDeclaration[] { type }; + } else { + TypeDeclaration[] newArray = new TypeDeclaration[parent.memberTypes.length + 1]; + System.arraycopy(parent.memberTypes, 0, newArray, 0, parent.memberTypes.length); + newArray[parent.memberTypes.length] = type; + parent.memberTypes = newArray; + } + typeNode.add(type, Kind.TYPE); + } + private static final char[] ALL = "all".toCharArray(); public static Annotation[] createSuppressWarningsAll(ASTNode source, Annotation[] originalAnnotationArray) { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 7cbaa5ac..2577befb 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -801,6 +801,19 @@ public class JavacHandlerUtil { typeNode.add(method, Kind.METHOD); } + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + */ + public static void injectType(final JavacNode typeNode, final JCClassDecl type) { + JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); + addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type)); + typeDecl.defs = typeDecl.defs.append(type); + typeNode.add(type, Kind.TYPE); + } + private static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source) { TreeMaker maker = node.getTreeMaker(); JCExpression suppressWarningsType = chainDots(node, "java", "lang", "SuppressWarnings"); -- cgit From 2d76b1d22dea1e78326ebafdb48967512183cede Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 21:12:01 +0200 Subject: First steps Builder support --- .../lombok/eclipse/handlers/HandleBuilder.java | 71 +++++++++++++ src/core/lombok/experimental/Builder.java | 112 +++++++++++++++++++++ src/utils/lombok/core/JavaIdentifiers.java | 57 +++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/core/lombok/eclipse/handlers/HandleBuilder.java create mode 100644 src/core/lombok/experimental/Builder.java create mode 100644 src/utils/lombok/core/JavaIdentifiers.java diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..13271165 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; + +import lombok.AccessLevel; +import lombok.core.AnnotationValues; +import lombok.core.ImmutableList; +import lombok.core.JavaIdentifiers; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; + +public class HandleBuilder extends EclipseAnnotationHandler { + @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { + String builderMethodName = annotation.getInstance().builderMethodName(); + if (builderMethodName == null) builderMethodName = "builder"; + if (builderMethodName.length() == 0) { + annotationNode.addError("builderMethodName cannot be the empty string."); + return; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(builderMethodName)) { + annotationNode.addError("builderMethodName must be a valid java method name."); + return; + } + + EclipseNode parent = annotationNode.up(); + + if (parent.get() instanceof ConstructorDeclaration) { + + } + + if (parent.get() instanceof MethodDeclaration) { + + } + + if (parent.get() instanceof TypeDeclaration) { + // TODO: How do we ensure this one will 'win' over the implicit constructors generated by @Data and @Value. + new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, true, Collections.emptyList(), ast); + } + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java new file mode 100644 index 00000000..b6667462 --- /dev/null +++ b/src/core/lombok/experimental/Builder.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.experimental; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class + * that contains a member which is annotated with {@code @Builder}. + *

+ * If a member is annotated, it must be either a constructor or a static method. If a class is annotated, + * then a private constructor is generated with all fields as arguments + * (as if {@code @AllArgsConstructor(AccessLevel.PRIVATE)} is present + * on the class), and it is as if this constructor has been annotated with {@code @Builder} instead. + *

+ * The effect of {@code @Builder} is that an inner class is generated named TBuilder, + * with a private constructor. Instances of TBuilder are made with the static + * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). + *

+ * The TBuilder class contains 1 method for each parameter of the annotated + * constructor / static method (each field, when annotating a class), which returns the builder itself. + * The builder also has a build() method which returns a completed instance of the original type, + * created by passing all parameters as set via the various other methods in the builder to the constructor + * or static method that was annotated with {@code @Builder}. The return type of this method will be the same + * as the relevant class, unless a static method has been annotated, in which case it'll be equal to the + * return type of that method. + *

+ * Complete documentation is found at the project lombok features page for @Builder. + *

+ *

+ * Before: + * + *

+ * @Builder
+ * class Example {
+ * 	private int foo;
+ * 	private final String bar;
+ * }
+ * 
+ * + * After: + * + *
+ * class Example<T> {
+ * 	private T foo;
+ * 	private final String bar;
+ * 	
+ * 	private Example(T foo, String bar) {
+ * 		this.foo = foo;
+ * 		this.bar = bar;
+ * 	}
+ * 	
+ * 	public static <T> ExampleBuilder<T> builder() {
+ * 		return new ExampleBuilder<T>();
+ * 	}
+ * 	
+ * 	public static class ExampleBuilder<T> {
+ * 		private T foo;
+ * 		private String bar;
+ * 		
+ * 		private ExampleBuilder() {}
+ * 		
+ * 		public ExampleBuilder foo(T foo) {
+ * 			this.foo = foo;
+ * 			return this;
+ * 		}
+ * 		
+ * 		public ExampleBuilder bar(String bar) {
+ * 			this.bar = bar;
+ * 			return this;
+ * 		}
+ * 		
+ * 		@java.lang.Override public String toString() {
+ * 			return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
+ * 		}
+ * 		
+ * 		public Example build() {
+ * 			return new Example(foo, bar);
+ * 		}
+ * 	}
+ * }
+ * 
+ */ +@Target({TYPE, METHOD, CONSTRUCTOR}) +@Retention(SOURCE) +public @interface Builder { + /** Name of the static method that creates a new builder instance. Default: {@code builder}. */ + String builderMethodName() default "builder"; +} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java new file mode 100644 index 00000000..dfec8815 --- /dev/null +++ b/src/utils/lombok/core/JavaIdentifiers.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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; + +/** + * Utility functions for validating potential java verifiers. + */ +public class JavaIdentifiers { + private JavaIdentifiers() {} + + private static final ImmutableList KEYWORDS = ImmutableList.of( + "public", "private", "protected", + "default", "switch", "case", + "for", "do", "goto", "const", "strictfp", "while", "if", "else", + "byte", "short", "int", "long", "float", "double", "void", "boolean", "char", + "null", "false", "true", + "continue", "break", "return", "instanceof", + "synchronized", "volatile", "transient", "final", "static", + "interface", "class", "extends", "implements", "throws", + "throw", "catch", "try", "finally", "abstract", "assert", + "enum", "import", "package", "native", "new", "super", "this"); + + public static boolean isValidJavaIdentifier(String identifier) { + if (identifier == null) return false; + if (identifier.isEmpty()) return false; + + if (!Character.isJavaIdentifierStart(identifier.charAt(0))) return false; + for (int i = 1; i < identifier.length(); i++) { + if (!Character.isJavaIdentifierPart(identifier.charAt(i))) return false; + } + + return !isKeyword(identifier); + } + + public static boolean isKeyword(String keyword) { + return KEYWORDS.contains(keyword); + } +} -- cgit From 648c3eeee69bede925f794b16b1f3d184359761f Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 10 Jun 2013 23:14:23 +0200 Subject: Eclipse Builder implementation finished. Tests need fleshing out though. --- .../eclipse/handlers/EclipseHandlerUtil.java | 31 +- .../lombok/eclipse/handlers/HandleBuilder.java | 323 ++++++++++++++++++++- .../lombok/eclipse/handlers/HandleConstructor.java | 55 ++-- src/core/lombok/eclipse/handlers/HandleData.java | 3 +- src/core/lombok/eclipse/handlers/HandleSetter.java | 2 +- src/core/lombok/eclipse/handlers/HandleValue.java | 3 +- src/core/lombok/experimental/Builder.java | 9 + test/transform/resource/before/BuilderComplex.java | 7 + test/transform/resource/before/BuilderSimple.java | 9 + 9 files changed, 387 insertions(+), 55 deletions(-) create mode 100644 test/transform/resource/before/BuilderComplex.java create mode 100644 test/transform/resource/before/BuilderSimple.java diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index f9295150..364ce0a5 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -359,6 +359,20 @@ public class EclipseHandlerUtil { return out; } + public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) { + if (params != null && params.length > 0) { + TypeReference[] refs = new TypeReference[params.length]; + int idx = 0; + for (TypeParameter param : params) { + TypeReference typeRef = new SingleTypeReference(param.name, p); + refs[idx++] = typeRef; + } + return new ParameterizedSingleTypeReference(typeName, refs, 0, p); + } + + return new SingleTypeReference(typeName, p); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -1208,15 +1222,15 @@ public class EclipseHandlerUtil { * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { field.annotations = createSuppressWarningsAll(field, field.annotations); - injectField(type, field); + return injectField(type, field); } /** * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectField(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) { TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.fields == null) { @@ -1243,7 +1257,7 @@ public class EclipseHandlerUtil { } } - type.add(field, Kind.FIELD); + return type.add(field, Kind.FIELD); } private static boolean isEnumConstant(final FieldDeclaration field) { @@ -1253,7 +1267,7 @@ public class EclipseHandlerUtil { /** * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) { + public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { method.annotations = createSuppressWarningsAll(method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); @@ -1286,7 +1300,7 @@ public class EclipseHandlerUtil { parent.methods = newArray; } - type.add(method, Kind.METHOD); + return type.add(method, Kind.METHOD); } /** @@ -1295,7 +1309,7 @@ public class EclipseHandlerUtil { * @param typeNode parent type to inject new type into * @param type New type (class, interface, etc) to inject. */ - public static void injectType(final EclipseNode typeNode, final TypeDeclaration type) { + public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) { type.annotations = createSuppressWarningsAll(type, type.annotations); TypeDeclaration parent = (TypeDeclaration) typeNode.get(); @@ -1307,7 +1321,8 @@ public class EclipseHandlerUtil { newArray[parent.memberTypes.length] = type; parent.memberTypes = newArray; } - typeNode.add(type, Kind.TYPE); + + return typeNode.add(type, Kind.TYPE); } private static final char[] ALL = "all".toCharArray(); diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 13271165..929168da 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -21,51 +21,344 @@ */ package lombok.eclipse.handlers; +import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +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.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +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.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; 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.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; +import lombok.core.AST.Kind; import lombok.core.AnnotationValues; -import lombok.core.ImmutableList; import lombok.core.JavaIdentifiers; +import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.Builder; +@ProviderFor(EclipseAnnotationHandler.class) public class HandleBuilder extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { - String builderMethodName = annotation.getInstance().builderMethodName(); + long p = (long) ast.sourceStart << 32 | ast.sourceEnd; + + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + if (builderMethodName == null) builderMethodName = "builder"; - if (builderMethodName.length() == 0) { - annotationNode.addError("builderMethodName cannot be the empty string."); + if (buildMethodName == null) builderMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + checkName("builderMethodName", builderMethodName, annotationNode); + checkName("buildMethodName", buildMethodName, annotationNode); + if (!builderClassName.isEmpty()) checkName("builderClassName", builderClassName, annotationNode); + + EclipseNode parent = annotationNode.up(); + + List typesOfParameters = new ArrayList(); + List namesOfParameters = new ArrayList(); + TypeReference returnType; + TypeParameter[] typeParams; + TypeReference[] thrownExceptions; + char[] nameOfStaticBuilderMethod; + EclipseNode tdParent; + + AbstractMethodDeclaration fillParametersFrom = null; + + if (parent.get() instanceof TypeDeclaration) { + tdParent = parent; + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.emptyList(), ast); + + for (EclipseNode fieldNode : HandleConstructor.findAllFields(parent)) { + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.type); + } + + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = null; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder"; + } else if (parent.get() instanceof ConstructorDeclaration) { + ConstructorDeclaration cd = (ConstructorDeclaration) parent.get(); + if (cd.typeParameters != null && cd.typeParameters.length > 0) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + + tdParent = parent.up(); + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + fillParametersFrom = cd; + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = cd.thrownExceptions; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder"; + } else if (parent.get() instanceof MethodDeclaration) { + MethodDeclaration md = (MethodDeclaration) parent.get(); + tdParent = parent.up(); + if (!md.isStatic()) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + fillParametersFrom = md; + returnType = copyType(md.returnType, ast); + typeParams = md.typeParameters; + thrownExceptions = md.thrownExceptions; + nameOfStaticBuilderMethod = md.selector; + if (builderClassName.isEmpty()) { + char[] token; + if (md.returnType instanceof QualifiedTypeReference) { + char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens; + token = tokens[tokens.length - 1]; + } else if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) { + for (TypeParameter tp : typeParams) { + if (Arrays.equals(tp.name, token)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + } + } else { + annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem."); + return; + } + + if (Character.isLowerCase(token[0])) { + char[] newToken = new char[token.length]; + System.arraycopy(token, 1, newToken, 1, token.length - 1); + newToken[0] = Character.toTitleCase(token[0]); + token = newToken; + } + + builderClassName = new String(token) + "Builder"; + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); return; } - if (!JavaIdentifiers.isValidJavaIdentifier(builderMethodName)) { - annotationNode.addError("builderMethodName must be a valid java method name."); - return; + if (fillParametersFrom != null) { + if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { + namesOfParameters.add(a.name); + typesOfParameters.add(a.type); + } } - EclipseNode parent = annotationNode.up(); + EclipseNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + List newMethods = new ArrayList(); + for (EclipseNode fieldNode : fieldNodes) { + MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast); + if (newMethod != null) newMethods.add(newMethod); + } - if (parent.get() instanceof ConstructorDeclaration) { - + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.emptyList(), true, ast, Collections.emptyList()); + if (cd != null) injectMethod(builderType, cd); } - if (parent.get() instanceof MethodDeclaration) { - + for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + if (md != null) injectMethod(builderType, md); } - if (parent.get() instanceof TypeDeclaration) { - // TODO: How do we ensure this one will 'win' over the implicit constructors generated by @Data and @Value. - new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, true, Collections.emptyList(), ast); + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + + + // create builder method in parent. + } + + private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.typeParameters = copyTypeParams(typeParams, source); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); + return out; + } + + private MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + + out.modifiers = ClassFileConstants.AccPublic; + TypeDeclaration typeDecl = (TypeDeclaration) type.get(); + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions, source); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; + + List assigns = new ArrayList(); + for (char[] fieldName : fieldNames) { + SingleNameReference nameRef = new SingleNameReference(fieldName, p); + assigns.add(nameRef); + } + + Statement statement; + + if (staticName == null) { + AllocationExpression allocationStatement = new AllocationExpression(); + allocationStatement.type = copyType(out.returnType, source); + allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p); + } else { + MessageSend invoke = new MessageSend(); + invoke.selector = staticName; + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); + TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; + if (tps != null) { + TypeReference[] trs = new TypeReference[tps.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(tps[i].name, p); + } + invoke.typeArguments = trs; + } + invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) { + statement = invoke; + } else { + statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + } + } + + out.statements = new Statement[] { statement }; + + out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + return out; + } + + private List addFieldsToBuilder(EclipseNode builderType, List namesOfParameters, List typesOfParameters, ASTNode source) { + int len = namesOfParameters.size(); + TypeDeclaration td = (TypeDeclaration) builderType.get(); + FieldDeclaration[] existing = td.fields; + if (existing == null) existing = new FieldDeclaration[0]; + + List out = new ArrayList(); + + top: + for (int i = len - 1; i >= 0; i--) { + char[] name = namesOfParameters.get(i); + for (FieldDeclaration exists : existing) { + if (Arrays.equals(exists.name, name)) { + out.add(builderType.getNodeFor(exists)); + continue top; + } + } + TypeReference fieldReference = copyType(typesOfParameters.get(i), source); + FieldDeclaration newField = new FieldDeclaration(name, 0, 0); + newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + newField.modifiers = ClassFileConstants.AccPrivate; + newField.type = fieldReference; + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + + return out; + } + + private static final AbstractMethodDeclaration[] EMPTY = {}; + + private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source) { + TypeDeclaration td = (TypeDeclaration) builderType.get(); + AbstractMethodDeclaration[] existing = td.methods; + if (existing == null) existing = EMPTY; + int len = existing.length; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + char[] name = fd.name; + + for (int i = 0; i < len; i++) { + if (!(existing[i] instanceof MethodDeclaration)) continue; + char[] existingName = existing[i].selector; + if (Arrays.equals(name, existingName)) return null; + } + + return HandleSetter.createSetter(td, fieldNode, fieldNode.getName(), true, ClassFileConstants.AccPublic, + source, Collections.emptyList(), Collections.emptyList()); + } + + private EclipseNode findInnerClass(EclipseNode parent, String name) { + char[] c = name.toCharArray(); + for (EclipseNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + TypeDeclaration td = (TypeDeclaration) child.get(); + if (Arrays.equals(td.name, c)) return child; + } + return null; + } + + private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + builder.typeParameters = copyTypeParams(typeParams, source); + builder.name = builderClassName.toCharArray(); + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } + + private static void checkName(String nameSpec, String identifier, EclipseNode annotationNode) { + if (identifier.isEmpty()) { + annotationNode.addError(nameSpec + " cannot be the empty string."); + return; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) { + annotationNode.addError(nameSpec + " must be a valid java identifier."); + return; } } } diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 8ccad77f..1ae680d9 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -39,6 +39,7 @@ import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; @@ -53,18 +54,14 @@ 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.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; 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.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; 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; @@ -82,7 +79,7 @@ public class HandleConstructor { List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast); } } @@ -100,7 +97,7 @@ public class HandleConstructor { List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -117,7 +114,7 @@ public class HandleConstructor { return fields; } - private static List findAllFields(EclipseNode typeNode) { + static List findAllFields(EclipseNode typeNode) { List fields = new ArrayList(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -146,7 +143,7 @@ public class HandleConstructor { List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -164,25 +161,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List onConstructor, ASTNode source) { + public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List onConstructor, ASTNode source) { generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List onConstructor, ASTNode source) { + public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List onConstructor, ASTNode source) { generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateConstructor(EclipseNode typeNode, AccessLevel level, List fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List onConstructor, ASTNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateConstructor(EclipseNode typeNode, AccessLevel level, List fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List onConstructor, ASTNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child)); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -235,7 +241,7 @@ public class HandleConstructor { return new Annotation[] { ann }; } - private ConstructorDeclaration createConstructor( + static ConstructorDeclaration createConstructor( AccessLevel level, EclipseNode type, Collection fields, boolean suppressConstructorProperties, ASTNode source, List onConstructor) { @@ -307,7 +313,7 @@ public class HandleConstructor { return constructor; } - private boolean isLocalType(EclipseNode type) { + private static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); @@ -321,18 +327,9 @@ public class HandleConstructor { MethodDeclaration constructor = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); - constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC; + constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; 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); - 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); + constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p); constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 3a43bd3f..aa309489 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -28,6 +28,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler { new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.emptyList(), ast); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 9b46b704..ae846a4e 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler { injectMethod(fieldNode.up(), method); } - private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List onMethod, List onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List onMethod, List onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index b69b1669..60938649 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -30,6 +30,7 @@ import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.NonFinal; import lombok.experimental.Value; @@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler { new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.emptyList(), ast); } } diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java index b6667462..5f2d1ca6 100644 --- a/src/core/lombok/experimental/Builder.java +++ b/src/core/lombok/experimental/Builder.java @@ -109,4 +109,13 @@ import java.lang.annotation.Target; public @interface Builder { /** Name of the static method that creates a new builder instance. Default: {@code builder}. */ String builderMethodName() default "builder"; + + /** Name of the instance method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + String buildMethodName() default "build"; + + /** Name of the builder class. + * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. + * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}. + */ + String builderClassName() default ""; } diff --git a/test/transform/resource/before/BuilderComplex.java b/test/transform/resource/before/BuilderComplex.java new file mode 100644 index 00000000..3d3e7187 --- /dev/null +++ b/test/transform/resource/before/BuilderComplex.java @@ -0,0 +1,7 @@ +import java.util.List; +import lombok.experimental.Builder; + +class BuilderComplex { + @Builder + private static void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) {} +} diff --git a/test/transform/resource/before/BuilderSimple.java b/test/transform/resource/before/BuilderSimple.java new file mode 100644 index 00000000..c749bb6c --- /dev/null +++ b/test/transform/resource/before/BuilderSimple.java @@ -0,0 +1,9 @@ +import java.util.List; + +@lombok.experimental.Builder +class BuilderSimple { + private final int noshow = 0; + private final int yes; + private List also; + private int $butNotMe; +} -- cgit From 9066d57ed9073cd99d664b2676d6fde54af1a7b6 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 14 Jun 2013 15:21:17 +0200 Subject: improved and added to test cases for @Builder. Eclipse's implementation continues to pass them all. --- .../resource/after-ecj/BuilderComplex.java | 40 ++++++++++++++++++++++ .../resource/after-ecj/BuilderSimple.java | 33 ++++++++++++++++++ .../after-ecj/BuilderWithExistingBuilderClass.java | 33 ++++++++++++++++++ test/transform/resource/before/BuilderComplex.java | 2 +- 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/transform/resource/after-ecj/BuilderComplex.java create mode 100644 test/transform/resource/after-ecj/BuilderSimple.java create mode 100644 test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java diff --git a/test/transform/resource/after-ecj/BuilderComplex.java b/test/transform/resource/after-ecj/BuilderComplex.java new file mode 100644 index 00000000..ca302a3d --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderComplex.java @@ -0,0 +1,40 @@ +import java.util.List; +import lombok.experimental.Builder; +class BuilderComplex { + public static @java.lang.SuppressWarnings("all") class VoidBuilder { + private T number; + private int arg2; + private String arg3; + private BuilderComplex selfRef; + @java.lang.SuppressWarnings("all") VoidBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") VoidBuilder number(final T number) { + this.number = number; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder arg2(final int arg2) { + this.arg2 = arg2; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder selfRef(final BuilderComplex selfRef) { + this.selfRef = selfRef; + return this; + } + public @java.lang.SuppressWarnings("all") void execute() { + BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); + } + } + BuilderComplex() { + super(); + } + private static @Builder(buildMethodName = "execute") void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) { + } + public static @java.lang.SuppressWarnings("all") VoidBuilder builder() { + return new VoidBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java new file mode 100644 index 00000000..4ca8e120 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -0,0 +1,33 @@ +import java.util.List; +@lombok.experimental.Builder class BuilderSimple { + public static @java.lang.SuppressWarnings("all") class BuilderSimpleBuilder { + private int yes; + private List also; + @java.lang.SuppressWarnings("all") BuilderSimpleBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSimpleBuilder yes(final int yes) { + this.yes = yes; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSimpleBuilder also(final List also) { + this.also = also; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSimple build() { + return new BuilderSimple(yes, also); + } + } + private final int noshow = 0; + private final int yes; + private List also; + private int $butNotMe; + private @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List also) { + super(); + this.yes = yes; + this.also = also; + } + public static @java.lang.SuppressWarnings("all") BuilderSimpleBuilder builder() { + return new BuilderSimpleBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..02ed259e --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -0,0 +1,33 @@ +import lombok.experimental.Builder; +class BuilderWithExistingBuilderClass { + public static class BuilderWithExistingBuilderClassBuilder { + private boolean arg2; + private String arg3; + private Z arg1; + public void arg2(boolean arg) { + } + @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder arg1(final Z arg1) { + this.arg1 = arg1; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClass build() { + return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); + } + } + BuilderWithExistingBuilderClass() { + super(); + } + public static @Builder BuilderWithExistingBuilderClass staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + public static @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder builder() { + return new BuilderWithExistingBuilderClassBuilder(); + } +} \ No newline at end of file diff --git a/test/transform/resource/before/BuilderComplex.java b/test/transform/resource/before/BuilderComplex.java index 3d3e7187..590a2723 100644 --- a/test/transform/resource/before/BuilderComplex.java +++ b/test/transform/resource/before/BuilderComplex.java @@ -2,6 +2,6 @@ import java.util.List; import lombok.experimental.Builder; class BuilderComplex { - @Builder + @Builder(buildMethodName = "execute") private static void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) {} } -- cgit From 359b7845f21ac7ad023ce1a13af8f6b5d1833068 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 16 Jun 2013 10:54:33 +0200 Subject: Most of the javac implementation for HandleBuilder, plus some minor updates and refactoring in the eclipse HandleBuilder. --- src/core/lombok/core/handlers/HandlerUtil.java | 48 +++++ .../lombok/eclipse/handlers/HandleBuilder.java | 29 +-- src/core/lombok/javac/handlers/HandleBuilder.java | 198 +++++++++++++++++++++ .../lombok/javac/handlers/HandleConstructor.java | 38 ++-- src/core/lombok/javac/handlers/HandleData.java | 3 +- src/core/lombok/javac/handlers/HandleValue.java | 3 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 19 +- .../before/BuilderWithExistingBuilderClass.java | 15 ++ 8 files changed, 314 insertions(+), 39 deletions(-) create mode 100644 src/core/lombok/core/handlers/HandlerUtil.java create mode 100644 src/core/lombok/javac/handlers/HandleBuilder.java create mode 100644 test/transform/resource/before/BuilderWithExistingBuilderClass.java diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java new file mode 100644 index 00000000..3d386054 --- /dev/null +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.handlers; + +import lombok.core.JavaIdentifiers; +import lombok.core.LombokNode; + +public class HandlerUtil { + private HandlerUtil() {} + + /** Checks if the given name is a valid identifier. + * + * If it is, this returns {@code true} and does nothing else. + * If it isn't, this returns {@code false} and adds an error message to the supplied node. + */ + public static boolean checkName(String nameSpec, String identifier, LombokNode errorNode) { + if (identifier.isEmpty()) { + errorNode.addError(nameSpec + " cannot be the empty string."); + return false; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) { + errorNode.addError(nameSpec + " must be a valid java identifier."); + return false; + } + + return true; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 929168da..d15f00e6 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.util.ArrayList; @@ -57,7 +58,6 @@ import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; -import lombok.core.JavaIdentifiers; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -78,9 +78,11 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (buildMethodName == null) builderMethodName = "build"; if (builderClassName == null) builderClassName = ""; - checkName("builderMethodName", builderMethodName, annotationNode); - checkName("buildMethodName", buildMethodName, annotationNode); - if (!builderClassName.isEmpty()) checkName("builderClassName", builderClassName, annotationNode); + if (checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } EclipseNode parent = annotationNode.up(); @@ -97,9 +99,9 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); - new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.emptyList(), ast); - for (EclipseNode fieldNode : HandleConstructor.findAllFields(parent)) { + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); namesOfParameters.add(fd.name); typesOfParameters.add(fd.type); @@ -202,9 +204,6 @@ public class HandleBuilder extends EclipseAnnotationHandler { MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); } - - - // create builder method in parent. } private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { @@ -349,16 +348,4 @@ public class HandleBuilder extends EclipseAnnotationHandler { builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); return injectType(tdParent, builder); } - - private static void checkName(String nameSpec, String identifier, EclipseNode annotationNode) { - if (identifier.isEmpty()) { - annotationNode.addError(nameSpec + " cannot be the empty string."); - return; - } - - if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) { - annotationNode.addError(nameSpec + " must be a valid java identifier."); - return; - } - } } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java new file mode 100644 index 00000000..c39255f2 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.ArrayList; + +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.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.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.experimental.Builder; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; + +import static lombok.javac.Javac.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +public class HandleBuilder extends JavacAnnotationHandler { + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) buildMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + JavacNode parent = annotationNode.up(); + + java.util.List typesOfParameters = new ArrayList(); + java.util.List namesOfParameters = new ArrayList(); + JCExpression returnType; + List typeParams = List.nil(); + List thrownExceptions = List.nil(); + Name nameOfStaticBuilderMethod; + JavacNode tdParent; + + JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null; + + if (parent.get() instanceof JCClassDecl) { + tdParent = parent; + JCClassDecl td = (JCClassDecl) tdParent.get(); + new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); + + for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.vartype); + } + + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = null; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("")) { + if (!fillParametersFrom.typarams.isEmpty()) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString(); + } else if (fillParametersFrom != null) { + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + returnType = fillParametersFrom.restype; + typeParams = fillParametersFrom.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = fillParametersFrom.name; + if (builderClassName.isEmpty()) { + if (returnType instanceof JCTypeApply) { + returnType = ((JCTypeApply) returnType).clazz; + } + if (returnType instanceof JCFieldAccess) { + builderClassName = ((JCFieldAccess) returnType).name.toString(); + } else if (returnType instanceof JCIdent) { + Name n = ((JCIdent) returnType).name; + + for (JCTypeParameter tp : typeParams) { + if (tp.name.contentEquals(n)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + builderClassName = n.toString(); + } else { + // This shouldn't happen. + System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); + builderClassName = td.name.toString(); + } + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + for (JCVariableDecl param : fillParametersFrom.params) { + namesOfParameters.add(param.name); + typesOfParameters.add(param.vartype); + } + } + + JavacNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + java.util.List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + java.util.List newMethods = new ArrayList(); + for (JavacNode fieldNode : fieldNodes) { + JCMethodDecl newMethod = makeSetterMethodForBuider(builderType, fieldNode, ast); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.nil(), builderType, List.nil(), true, ast); + if (cd != null) injectMethod(builderType, cd); + } + + for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + private JavacNode findInnerClass(JavacNode parent, String name) { + for (JavacNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + JCClassDecl td = (JCClassDecl) child.get(); + if (td.name.contentEquals(name)) return child; + } + return null; + } + + private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List typeParams, JCAnnotation ast) { + TreeMaker maker = tdParent.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); + JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), typeParams, null, List.nil(), List.nil()); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index bb883ca4..ecd982e9 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -29,6 +29,7 @@ import lombok.RequiredArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; +import lombok.experimental.Builder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; @@ -66,7 +67,7 @@ public class HandleConstructor { String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; List fields = List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, false, false, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, false, annotationNode); } } @@ -84,7 +85,7 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } @@ -119,11 +120,11 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } - private static List findAllFields(JavacNode typeNode) { + static List findAllFields(JavacNode typeNode) { ListBuffer fields = ListBuffer.lb(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -154,25 +155,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.nil(), findAllFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List onConstructor, List fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { + public void generateConstructor(JavacNode typeNode, AccessLevel level, List onConstructor, List fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (JavacNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -207,7 +217,7 @@ public class HandleConstructor { mods.annotations = mods.annotations.append(annotation); } - private JCMethodDecl createConstructor(AccessLevel level, List onConstructor, JavacNode typeNode, List fields, boolean suppressConstructorProperties, JCTree source) { + static JCMethodDecl createConstructor(AccessLevel level, List onConstructor, JavacNode typeNode, List fields, boolean suppressConstructorProperties, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; @@ -243,7 +253,7 @@ public class HandleConstructor { null, List.nil(), params.toList(), List.nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source); } - private boolean isLocalType(JavacNode type) { + private static boolean isLocalType(JavacNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java index 62183a15..858fb543 100644 --- a/src/core/lombok/javac/handlers/HandleData.java +++ b/src/core/lombok/javac/handlers/HandleData.java @@ -27,6 +27,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -50,7 +51,7 @@ public class HandleData extends JavacAnnotationHandler { String staticConstructorName = annotation.getInstance().staticConstructor(); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index f5b10bc1..a59865f7 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -29,6 +29,7 @@ import lombok.experimental.NonFinal; import lombok.experimental.Value; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -65,7 +66,7 @@ public class HandleValue extends JavacAnnotationHandler { new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 2577befb..23bc2daf 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -806,12 +806,13 @@ public class JavacHandlerUtil { * * @param typeNode parent type to inject new type into * @param type New type (class, interface, etc) to inject. + * @return */ - public static void injectType(final JavacNode typeNode, final JCClassDecl type) { + public static JavacNode injectType(final JavacNode typeNode, final JCClassDecl type) { JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type)); typeDecl.defs = typeDecl.defs.append(type); - typeNode.add(type, Kind.TYPE); + return typeNode.add(type, Kind.TYPE); } private static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source) { @@ -1025,6 +1026,20 @@ public class JavacHandlerUtil { return result.toList(); } + public static JCExpression namePlusTypeParamsToTypeReference(TreeMaker maker, Name typeName, List params) { + ListBuffer typeArgs = ListBuffer.lb(); + + if (!params.isEmpty()) { + for (JCTypeParameter param : params) { + typeArgs.append(maker.Ident(param.name)); + } + + return maker.TypeApply(maker.Ident(typeName), typeArgs.toList()); + } + + return maker.Ident(typeName); + } + static List copyAnnotations(List in) { ListBuffer out = ListBuffer.lb(); for (JCExpression expr : in) { diff --git a/test/transform/resource/before/BuilderWithExistingBuilderClass.java b/test/transform/resource/before/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..262e3b85 --- /dev/null +++ b/test/transform/resource/before/BuilderWithExistingBuilderClass.java @@ -0,0 +1,15 @@ +import lombok.experimental.Builder; + +class BuilderWithExistingBuilderClass { + @Builder + public static BuilderWithExistingBuilderClass staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + + public static class BuilderWithExistingBuilderClassBuilder { + private Z arg1; + + public void arg2(boolean arg) { + } + } +} \ No newline at end of file -- cgit From e047d2b94e1206bbf304725c20ee20afbd1681fb Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 18 Jun 2013 03:33:13 +0200 Subject: Added a ClassDef wrapper, because its signature changed between javac1.6 and javac1.7. (The wrapper uses reflection). Need for: javac @Builder impl. Also added some utilities to JavacHandlerUtil. --- .../lombok/javac/handlers/JavacHandlerUtil.java | 15 +++++--- src/utils/lombok/javac/Javac.java | 42 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 23bc2daf..92cebf4c 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -730,11 +730,11 @@ public class JavacHandlerUtil { * * Also takes care of updating the JavacAST. */ - public static void injectField(JavacNode typeNode, JCVariableDecl field) { - injectField(typeNode, field, false); + public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field) { + return injectField(typeNode, field, false); } - private static void injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { + private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { JCClassDecl type = (JCClassDecl) typeNode.get(); if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field)); @@ -760,7 +760,7 @@ public class JavacHandlerUtil { insertAfter.tail = fieldEntry; } - typeNode.add(field, Kind.FIELD); + return typeNode.add(field, Kind.FIELD); } private static boolean isEnumConstant(final JCVariableDecl field) { @@ -1026,6 +1026,13 @@ public class JavacHandlerUtil { return result.toList(); } + public static List copyTypeParams(TreeMaker maker, List params) { + if (params == null || params.isEmpty()) return params; + ListBuffer out = ListBuffer.lb(); + for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds)); + return out.toList(); + } + public static JCExpression namePlusTypeParamsToTypeReference(TreeMaker maker, Name typeName, List params) { ListBuffer typeArgs = ListBuffer.lb(); diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 08c7c957..4f316d9f 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -22,17 +22,25 @@ package lombok.javac; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree; +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.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; /** * Container for static utility methods relevant to lombok's operation on javac. @@ -161,4 +169,38 @@ public class Javac { } } + private static Method method; + + public static JCClassDecl ClassDef(TreeMaker maker, JCModifiers mods, Name name, List typarams, JCExpression extending, List implementing, List defs) { + if (method == null) try { + method = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCExpression.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + if (method == null) try { + method = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCTree.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + + if (method == null) throw new IllegalStateException("Lombok bug #20130617-1310: ClassDef doesn't look like anything we thought it would look like."); + if (!Modifier.isPublic(method.getModifiers()) && !method.isAccessible()) { + method.setAccessible(true); + } + + try { + return (JCClassDecl) method.invoke(maker, mods, name, typarams, extending, implementing, defs); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (IllegalAccessException e) { + throw sneakyThrow(e.getCause()); + } + } + + private static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Javac.sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } } -- cgit From eb3d32c718d9ef46fd30bc677147cda85318fb9c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 18 Jun 2013 03:33:35 +0200 Subject: finished tests for builder (added after-delombok versions). --- .../resource/after-delombok/BuilderComplex.java | 43 ++++++++++++++++++++++ .../resource/after-delombok/BuilderSimple.java | 38 +++++++++++++++++++ .../BuilderWithExistingBuilderClass.java | 33 +++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 test/transform/resource/after-delombok/BuilderComplex.java create mode 100644 test/transform/resource/after-delombok/BuilderSimple.java create mode 100644 test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java diff --git a/test/transform/resource/after-delombok/BuilderComplex.java b/test/transform/resource/after-delombok/BuilderComplex.java new file mode 100644 index 00000000..d6d12ebf --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderComplex.java @@ -0,0 +1,43 @@ +import java.util.List; +class BuilderComplex { + private static void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) { + } + @java.lang.SuppressWarnings("all") + public static class VoidBuilder { + private T number; + private int arg2; + private String arg3; + private BuilderComplex selfRef; + @java.lang.SuppressWarnings("all") + VoidBuilder() { + } + @java.lang.SuppressWarnings("all") + public VoidBuilder number(final T number) { + this.number = number; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder arg2(final int arg2) { + this.arg2 = arg2; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder selfRef(final BuilderComplex selfRef) { + this.selfRef = selfRef; + return this; + } + @java.lang.SuppressWarnings("all") + public void execute() { + BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); + } + } + @java.lang.SuppressWarnings("all") + public static VoidBuilder builder() { + return new VoidBuilder(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java new file mode 100644 index 00000000..113d538e --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -0,0 +1,38 @@ +import java.util.List; +class BuilderSimple { + private final int noshow = 0; + private final int yes; + private List also; + private int $butNotMe; + @java.lang.SuppressWarnings("all") + private BuilderSimple(final int yes, final List also) { + this.yes = yes; + this.also = also; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSimpleBuilder { + private int yes; + private List also; + @java.lang.SuppressWarnings("all") + BuilderSimpleBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSimpleBuilder yes(final int yes) { + this.yes = yes; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSimpleBuilder also(final List also) { + this.also = also; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSimple build() { + return new BuilderSimple(yes, also); + } + } + @java.lang.SuppressWarnings("all") + public static BuilderSimpleBuilder builder() { + return new BuilderSimpleBuilder(); + } +} diff --git a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..a8800009 --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java @@ -0,0 +1,33 @@ +class BuilderWithExistingBuilderClass { + public static BuilderWithExistingBuilderClass staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + public static class BuilderWithExistingBuilderClassBuilder { + private boolean arg2; + private String arg3; + private Z arg1; + public void arg2(boolean arg) { + } + @java.lang.SuppressWarnings("all") + BuilderWithExistingBuilderClassBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClassBuilder arg1(final Z arg1) { + this.arg1 = arg1; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClassBuilder arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClass build() { + return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); + } + } + @java.lang.SuppressWarnings("all") + public static BuilderWithExistingBuilderClassBuilder builder() { + return new BuilderWithExistingBuilderClassBuilder(); + } +} \ No newline at end of file -- cgit From e1c39bbc601408decb0ae147d181708a5af41307 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 18 Jun 2013 04:23:15 +0200 Subject: javac builder implementation. Passes all tests. Added toString() impl for builders in both eclipse and javac. Added all documentation, though it'll need some reviewing. --- buildScripts/website.ant.xml | 3 + doc/changelog.markdown | 1 + .../lombok/eclipse/handlers/HandleBuilder.java | 7 +- .../lombok/eclipse/handlers/HandleToString.java | 8 +- src/core/lombok/javac/handlers/HandleBuilder.java | 133 +++++++++++++++++++-- src/core/lombok/javac/handlers/HandleSetter.java | 12 +- src/core/lombok/javac/handlers/HandleToString.java | 6 +- .../resource/after-delombok/BuilderComplex.java | 5 + .../resource/after-delombok/BuilderSimple.java | 5 + .../BuilderWithExistingBuilderClass.java | 5 + .../resource/after-ecj/BuilderComplex.java | 3 + .../resource/after-ecj/BuilderSimple.java | 3 + .../after-ecj/BuilderWithExistingBuilderClass.java | 3 + .../experimental/BuilderExample_post.jpage | 40 +++++++ .../experimental/BuilderExample_pre.jpage | 7 ++ website/features/experimental/Accessors.html | 2 +- website/features/experimental/Builder.html | 127 ++++++++++++++++++++ website/features/experimental/index.html | 2 + 18 files changed, 350 insertions(+), 22 deletions(-) create mode 100644 usage_examples/experimental/BuilderExample_post.jpage create mode 100644 usage_examples/experimental/BuilderExample_pre.jpage create mode 100644 website/features/experimental/Builder.html diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 41130bd2..521ca59b 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -148,6 +148,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index aaf66030..85fcd86a 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d15f00e6..e2bf5fe2 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -78,7 +78,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (buildMethodName == null) builderMethodName = "build"; if (builderClassName == null) builderClassName = ""; - if (checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; if (!builderClassName.isEmpty()) { if (!checkName("builderClassName", builderClassName, annotationNode)) return; @@ -200,6 +200,11 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (md != null) injectMethod(builderType, md); } + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) injectMethod(builderType, md); + } + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index d864153f..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler { } } - private MethodDeclaration createToString(EclipseNode type, Collection fields, + static MethodDeclaration createToString(EclipseNode type, Collection fields, boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); @@ -282,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler { return method; } - private String getTypeName(EclipseNode type) { + private static String getTypeName(EclipseNode type) { String typeName = getSingleTypeName(type); EclipseNode upType = type.up(); while (upType.getKind() == Kind.TYPE) { @@ -292,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler { return typeName; } - private String getSingleTypeName(EclipseNode type) { + private static String getSingleTypeName(EclipseNode type) { TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); char[] rawTypeName = typeDeclaration.name; return rawTypeName == null ? "" : new String(rawTypeName); @@ -301,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler { 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) { + private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; NameReference ref; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index c39255f2..aa485b26 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -22,21 +22,28 @@ package lombok.javac.handlers; import java.util.ArrayList; +import java.util.Collections; + +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.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.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.JCPrimitiveTypeTree; +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.tree.TreeMaker; import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; import lombok.AccessLevel; @@ -51,6 +58,7 @@ import static lombok.javac.Javac.*; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; +@ProviderFor(JavacAnnotationHandler.class) public class HandleBuilder extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { Builder builderInstance = annotation.getInstance(); @@ -68,6 +76,9 @@ public class HandleBuilder extends JavacAnnotationHandler { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } + deleteAnnotationIfNeccessary(annotationNode, Builder.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + JavacNode parent = annotationNode.up(); java.util.List typesOfParameters = new ArrayList(); @@ -93,7 +104,7 @@ public class HandleBuilder extends JavacAnnotationHandler { returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; - thrownExceptions = null; + thrownExceptions = List.nil(); nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("")) { @@ -107,7 +118,7 @@ public class HandleBuilder extends JavacAnnotationHandler { typeParams = td.typarams; thrownExceptions = fillParametersFrom.thrown; nameOfStaticBuilderMethod = null; - if (builderClassName.isEmpty()) builderClassName = td.name.toString(); + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null) { tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); @@ -124,21 +135,27 @@ public class HandleBuilder extends JavacAnnotationHandler { returnType = ((JCTypeApply) returnType).clazz; } if (returnType instanceof JCFieldAccess) { - builderClassName = ((JCFieldAccess) returnType).name.toString(); + builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder"; } else if (returnType instanceof JCIdent) { Name n = ((JCIdent) returnType).name; for (JCTypeParameter tp : typeParams) { - if (tp.name.contentEquals(n)) { + if (tp.name.equals(n)) { annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); return; } } - builderClassName = n.toString(); + builderClassName = n.toString() + "Builder"; + } else if (returnType instanceof JCPrimitiveTypeTree) { + builderClassName = returnType.toString() + "Builder"; + if (Character.isLowerCase(builderClassName.charAt(0))) { + builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); + } + } else { // This shouldn't happen. System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); - builderClassName = td.name.toString(); + builderClassName = td.name.toString() + "Builder"; } } } else { @@ -158,7 +175,7 @@ public class HandleBuilder extends JavacAnnotationHandler { java.util.List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); java.util.List newMethods = new ArrayList(); for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuider(builderType, fieldNode, ast); + JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast); if (newMethod != null) newMethods.add(newMethod); } @@ -170,16 +187,112 @@ public class HandleBuilder extends JavacAnnotationHandler { for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); if (md != null) injectMethod(tdParent, md); } } + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List fieldNames, JavacNode type, List thrownExceptions) { + TreeMaker maker = type.getTreeMaker(); + + JCExpression call; + JCStatement statement; + + ListBuffer args = ListBuffer.lb(); + for (Name n : fieldNames) { + args.append(maker.Ident(n)); + } + + if (staticName == null) { + call = maker.NewClass(null, List.nil(), returnType, args.toList(), null); + statement = maker.Return(call); + } else { + ListBuffer typeParams = ListBuffer.lb(); + for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { + typeParams.append(maker.Ident(tp.name)); + } + + JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); + call = maker.Apply(typeParams.toList(), fn, args.toList()); + if (returnType instanceof JCPrimitiveTypeTree && ((JCPrimitiveTypeTree) returnType).typetag == CTC_VOID) { + statement = maker.Exec(call); + } else { + statement = maker.Return(call); + } + } + + JCBlock body = maker.Block(0, List.of(statement)); + + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.nil(), List.nil(), thrownExceptions, body, null); + } + + private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List typeParams) { + TreeMaker maker = type.getTreeMaker(); + + ListBuffer typeArgs = ListBuffer.lb(); + for (JCTypeParameter typeParam : typeParams) { + typeArgs.append(maker.Ident(typeParam.name)); + } + + JCExpression call = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.nil(), null); + JCStatement statement = maker.Return(call); + + JCBlock body = maker.Block(0, List.of(statement)); + return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.nil(), List.nil(), body, null); + } + + private java.util.List addFieldsToBuilder(JavacNode builderType, java.util.List namesOfParameters, java.util.List typesOfParameters, JCTree source) { + int len = namesOfParameters.size(); + java.util.List existing = new ArrayList(); + for (JavacNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + java.util.Listout = new ArrayList(); + + top: + for (int i = len - 1; i >= 0; i--) { + Name name = namesOfParameters.get(i); + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(name)) { + out.add(exists); + continue top; + } + } + TreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null); + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + return out; + } + + + private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) { + Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + for (JavacNode child : builderType.down()) { + if (child.getKind() != Kind.METHOD) continue; + Name existingName = ((JCMethodDecl) child.get()).name; + if (existingName.equals(fieldName)) return null; + } + + TreeMaker maker = builderType.getTreeMaker(); + return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.nil(), List.nil()); + } + private JavacNode findInnerClass(JavacNode parent, String name) { for (JavacNode child : parent.down()) { if (child.getKind() != Kind.TYPE) continue; @@ -192,7 +305,7 @@ public class HandleBuilder extends JavacAnnotationHandler { private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List typeParams, JCAnnotation ast) { TreeMaker maker = tdParent.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); - JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), typeParams, null, List.nil(), List.nil()); + JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.nil(), List.nil()); return injectType(tdParent, builder); } } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index c1e03c35..29728eae 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -194,9 +194,13 @@ public class HandleSetter extends JavacAnnotationHandler { injectMethod(fieldNode.up(), createdSetter); } - private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List onMethod, List onParam) { + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List onMethod, List onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); + return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + } + + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List onMethod, List onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -222,17 +226,17 @@ public class HandleSetter extends JavacAnnotationHandler { } JCExpression methodType = null; - if (returnThis) { + if (shouldReturnThis) { methodType = cloneSelfType(field); } if (methodType == null) { //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. methodType = treeMaker.Type(new JCNoType(CTC_VOID)); - returnThis = false; + shouldReturnThis = false; } - if (returnThis) { + if (shouldReturnThis) { JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); statements.append(returnStatement); } diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 5b3c033c..333393da 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -24,6 +24,8 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; +import java.util.Collection; + import lombok.ToString; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; @@ -164,7 +166,7 @@ public class HandleToString extends JavacAnnotationHandler { } } - private JCMethodDecl createToString(JavacNode typeNode, List fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + static JCMethodDecl createToString(JavacNode typeNode, Collection fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.nil()); @@ -241,7 +243,7 @@ public class HandleToString extends JavacAnnotationHandler { List.nil(), List.nil(), List.nil(), body, null), source); } - private String getTypeName(JavacNode typeNode) { + private static String getTypeName(JavacNode typeNode) { String typeName = ((JCClassDecl) typeNode.get()).name.toString(); JavacNode upType = typeNode.up(); while (upType.getKind() == Kind.TYPE) { diff --git a/test/transform/resource/after-delombok/BuilderComplex.java b/test/transform/resource/after-delombok/BuilderComplex.java index d6d12ebf..3c97f92a 100644 --- a/test/transform/resource/after-delombok/BuilderComplex.java +++ b/test/transform/resource/after-delombok/BuilderComplex.java @@ -35,6 +35,11 @@ class BuilderComplex { public void execute() { BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderComplex.VoidBuilder(number=" + this.number + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ", selfRef=" + this.selfRef + ")"; + } } @java.lang.SuppressWarnings("all") public static VoidBuilder builder() { diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java index 113d538e..24ac369c 100644 --- a/test/transform/resource/after-delombok/BuilderSimple.java +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -30,6 +30,11 @@ class BuilderSimple { public BuilderSimple build() { return new BuilderSimple(yes, also); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes + ", also=" + this.also + ")"; + } } @java.lang.SuppressWarnings("all") public static BuilderSimpleBuilder builder() { diff --git a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java index a8800009..1d40dbfa 100644 --- a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java @@ -25,6 +25,11 @@ class BuilderWithExistingBuilderClass { public BuilderWithExistingBuilderClass build() { return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1 + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ")"; + } } @java.lang.SuppressWarnings("all") public static BuilderWithExistingBuilderClassBuilder builder() { diff --git a/test/transform/resource/after-ecj/BuilderComplex.java b/test/transform/resource/after-ecj/BuilderComplex.java index ca302a3d..19aeb043 100644 --- a/test/transform/resource/after-ecj/BuilderComplex.java +++ b/test/transform/resource/after-ecj/BuilderComplex.java @@ -28,6 +28,9 @@ class BuilderComplex { public @java.lang.SuppressWarnings("all") void execute() { BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((("BuilderComplex.VoidBuilder(number=" + this.number) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ", selfRef=") + this.selfRef) + ")"); + } } BuilderComplex() { super(); diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java index 4ca8e120..228b1928 100644 --- a/test/transform/resource/after-ecj/BuilderSimple.java +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -17,6 +17,9 @@ import java.util.List; public @java.lang.SuppressWarnings("all") BuilderSimple build() { return new BuilderSimple(yes, also); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes) + ", also=") + this.also) + ")"); + } } private final int noshow = 0; private final int yes; diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java index 02ed259e..38cb0038 100644 --- a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -20,6 +20,9 @@ class BuilderWithExistingBuilderClass { public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClass build() { return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ")"); + } } BuilderWithExistingBuilderClass() { super(); diff --git a/usage_examples/experimental/BuilderExample_post.jpage b/usage_examples/experimental/BuilderExample_post.jpage new file mode 100644 index 00000000..624b236b --- /dev/null +++ b/usage_examples/experimental/BuilderExample_post.jpage @@ -0,0 +1,40 @@ +public class BuilderExample { + private String name; + private int age; + + BuilderExample(String name, int age) { + this.name = name; + this.age = age; + } + + public static BuilderExampleBuilder builder() { + return new BuilderExampleBuilder(); + } + + public static class BuilderExampleBuilder { + private String name; + private int age; + + BuilderExampleBuilder() { + } + + public BuilderExampleBuilder name(String name) { + this.name = name; + return this; + } + + public BuilderExampleBuilder age(int age) { + this.age = age; + return this; + } + + public BuilderExample build() { + return new BuilderExample(name, age); + } + + @java.lang.Override + public String toString() { + return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ")"; + } + } +} \ No newline at end of file diff --git a/usage_examples/experimental/BuilderExample_pre.jpage b/usage_examples/experimental/BuilderExample_pre.jpage new file mode 100644 index 00000000..9c754352 --- /dev/null +++ b/usage_examples/experimental/BuilderExample_pre.jpage @@ -0,0 +1,7 @@ +import lombok.experimental.Builder; + +@Builder +public class BuilderExample { + private String name; + private int age; +} diff --git a/website/features/experimental/Accessors.html b/website/features/experimental/Accessors.html index dce77d32..3ca79de5 100644 --- a/website/features/experimental/Accessors.html +++ b/website/features/experimental/Accessors.html @@ -84,7 +84,7 @@
diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html new file mode 100644 index 00000000..5ba74a27 --- /dev/null +++ b/website/features/experimental/Builder.html @@ -0,0 +1,127 @@ + + + + + + + + EXPERIMENTAL - @Builder +
+
+
+ +

@Builder

+ +
+

Since

+

+ @Builder was introduced as experimental feature in lombok v0.11.10. +

+
+
+

Experimental

+

+ Experimental because: +

    +
  • New feature - community feedback requested.
  • +
  • This feature will move to the core package soon.
  • +
+ Current status: sure thing - This feature will move to the core package soon. +
+
+

Overview

+

+ The @Builder annotation produces complex builder APIs for your classes. +

+ @Builder lets you automatically produce the code required to have your class be instantiable with code such as:
+ Person.builder().name("Adam Savage").city("San Francisco").worksAt("Mythbusters").build(); +

+ @Builder can be placed on a class, or on a constructor, or on a static method. While the "on a class" and "on a constructor" + mode are the most common use-case, @Builder is most easily explained with the "static method" use-case. +

+ A static method annotated with @Builder (from now on called the target) causes the following 7 things to be generated:

    +
  • An inner static class named FooBuilder, with the same type arguments as the static method (called the builder).
  • +
  • In the builder: One private non-static non-final field for each parameter of the target.
  • +
  • In the builder: A package private no-args empty constructor.
  • +
  • In the builder: A 'setter'-like method for each parmeter of the target: It has the same type as that parameter and the same name. + It returns the builder itself, so that the setter calls can be chained, as in the above example.
  • +
  • In the builder: A build() method which calls the static method, passing in each field. It returns the same type that the + target returns.
  • +
  • In the builder: A sensible toString() implementation.
  • +
  • In the class containing the target: A builder() method, which creates a new instance of the builder.
  • +
+ Each listed generated element will be silently skipped if that element already exists (disregarding parameter counts and looking only at names). This + includes the builder itself: If that class already exists, lombok will simply start injecting fields and methods inside this already existing + class, unless of course the fields / methods to be injected already exist. +

+ Now that the "static method" mode is clear, putting a @Builder annotation on a constructor functions similarly; effectively, + constructors are just static methods that have a special syntax to invoke them: Their 'return type' is the class they construct, and their + type parameters are the same as the type parameters of the class itself. +

+ Finally, applying @Builder to a class is as if you added @AllArgsConstructor(acces = AccessLevel.PACKAGE) to the class and applied the + @Builder annotation to this all-args-constructor. Note that this constructor is only generated if there is no explicit + constructor present in the so annotated class, but this @AllArgsConstructor has priority over any other implicitly + generated lombok constructor (such as @Data and @Value). If an explicit constructor is present, no constructor is generated, + but the builder will be created with the assumption that this constructor exists. If you've written another constructor, you'll get a compilation error.
+ The solution is to either let lombok write this constructor (delete your own), or, annotate your constructor instead. +

+ The name of the builder class is FoobarBuilder, where Foobar is the simplified, title-cased form of the return type of the + target - that is, the name of your type for @Builder on constructors and types, and the name of the return type for @Builder + on static methods. For example, if @Builder is applied to a class named com.yoyodyne.FancyList<T>, then the builder name will be + FancyListBuilder<T>. If @Builder is applied to a static method that returns void, the builder will be named + VoidBuilder. +

+ The only configurable aspect of builder are the builder's class name (default: return type + 'Builder'), the build() method's name, and the + builder() method's name:
+ @Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld") +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Another strategy for fluent APIs is that the programmer using your library statically imports your 'builder' method. In this case, you might want to name your builder + method equal to your type's name. So, the builder method for a class called Person would become person(). This is nicer if the builder method + is statically imported. +

+ If the return type of your target static method is a type parameter (such as T), lombok will enforce an explicit builder class name. +

+ You don't HAVE to use @Builder to build anything; you can for example put it on a static method that has lots of parameter to improve the API of it. + In this case, we suggest you use buildMethodName = to rename the build method to execute() instead. +

+ The builder class will NOT get an auto-generated implementation of hashCode or equals methods! These would suggest that it is sensible to use + instances of a builder as keys in a set or map. However, that's not a sensible thing to do. Hence, no hashCode or equals. +

+ Generics are sorted out for you. +

+
+
+ +
+
+
+ + + diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 24fbb541..d0a086a0 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -22,6 +22,8 @@ Features that receive positive community feedback and which seem to produce clean, flexible code will eventually become accepted as a core feature and move out of the experimental package.
+
@Builder
+
It's like drinking tea with an extended pinky while wearing a monocle: No-hassle fancy-pants APIs for object creation!
@Accessors
A more fluent API for getters and setters.
@ExtensionMethod
-- cgit From b5747963c022f680168ff66ebdc7860adb954882 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:23:02 +0200 Subject: Value has been promoted to the main package. --- doc/changelog.markdown | 2 +- src/core/lombok/Data.java | 4 +- src/core/lombok/Value.java | 58 ++++++++++++++++ src/core/lombok/core/LombokInternalAliasing.java | 52 ++++++++++++++ src/core/lombok/core/TypeLibrary.java | 6 ++ src/core/lombok/core/TypeResolver.java | 9 +-- src/core/lombok/eclipse/EclipseImportList.java | 13 ++-- src/core/lombok/eclipse/handlers/HandleValue.java | 2 +- src/core/lombok/javac/JavacImportList.java | 9 ++- src/core/lombok/javac/handlers/HandleValue.java | 4 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 19 ++++- src/delombok/lombok/delombok/DelombokApp.java | 1 + .../resource/after-delombok/ValueExperimental.java | 46 ++++++++++++ .../ValueExperimentalStarImport.java | 25 +++++++ .../resource/after-ecj/ValueExperimental.java | 39 +++++++++++ .../after-ecj/ValueExperimentalStarImport.java | 20 ++++++ test/transform/resource/after-ecj/ValuePlain.java | 4 +- .../resource/before/ValueExperimental.java | 9 +++ .../before/ValueExperimentalStarImport.java | 5 ++ test/transform/resource/before/ValuePlain.java | 4 +- website/features/Data.html | 2 +- website/features/SneakyThrows.html | 2 +- website/features/Value.html | 76 ++++++++++++++++++++ website/features/experimental/Value.html | 81 ---------------------- website/features/experimental/Wither.html | 2 +- website/features/experimental/index.html | 9 ++- website/features/experimental/onX.html | 2 +- website/features/index.html | 2 + 28 files changed, 396 insertions(+), 111 deletions(-) create mode 100644 src/core/lombok/Value.java create mode 100644 src/core/lombok/core/LombokInternalAliasing.java create mode 100644 test/transform/resource/after-delombok/ValueExperimental.java create mode 100644 test/transform/resource/after-delombok/ValueExperimentalStarImport.java create mode 100644 test/transform/resource/after-ecj/ValueExperimental.java create mode 100644 test/transform/resource/after-ecj/ValueExperimentalStarImport.java create mode 100644 test/transform/resource/before/ValueExperimental.java create mode 100644 test/transform/resource/before/ValueExperimentalStarImport.java create mode 100644 website/features/Value.html delete mode 100644 website/features/experimental/Value.html diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 85fcd86a..95ee5764 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,7 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) -* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). +* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index ee6f2fcb..bbc8d920 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -39,7 +39,7 @@ import java.lang.annotation.Target; * @see RequiredArgsConstructor * @see ToString * @see EqualsAndHashCode - * @see lombok.experimental.Value + * @see lombok.Value */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java new file mode 100644 index 00000000..2cffe15b --- /dev/null +++ b/src/core/lombok/Value.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012-2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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 a lot of code which fits with a class that is a representation of an immutable entity. + *

+ * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. + *

+ * Complete documentation is found at the project lombok features page for @Value. + * + * @see lombok.Getter + * @see lombok.experimental.FieldDefaults + * @see lombok.RequiredArgsConstructor + * @see lombok.ToString + * @see lombok.EqualsAndHashCode + * @see lombok.Data + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Value { + /** + * 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/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java new file mode 100644 index 00000000..4fd7b29d --- /dev/null +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.Collections; +import java.util.HashMap; +import java.util.Map; + +public class LombokInternalAliasing { + public static final Map IMPLIED_EXTRA_STAR_IMPORTS; + public static final Map ALIASES; + + /** + * Provide a fully qualified name (FQN), and the canonical version of this is returned. + */ + public static String processAliases(String in) { + if (in == null) return null; + for (Map.Entry e : ALIASES.entrySet()) { + if (in.equals(e.getKey())) return e.getValue(); + } + return in; + } + + static { + Map m = new HashMap(); + m.put("lombok.experimental", "lombok"); + IMPLIED_EXTRA_STAR_IMPORTS = Collections.unmodifiableMap(m); + + m = new HashMap(); + m.put("lombok.experimental.Value", "lombok.Value"); + ALIASES = Collections.unmodifiableMap(m); + } +} diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index a89091c4..c0e9dc43 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -74,6 +74,9 @@ public class TypeLibrary { unqualifiedToQualifiedMap.put(unqualified, fullyQualifiedTypeName); unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, fullyQualifiedTypeName); + for (Map.Entry e : LombokInternalAliasing.ALIASES.entrySet()) { + if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), fullyQualifiedTypeName); + } } /** @@ -85,6 +88,9 @@ public class TypeLibrary { public String toQualified(String typeReference) { if (unqualifiedToQualifiedMap == null) { if (typeReference.equals(unqualified) || typeReference.equals(qualified)) return qualified; + for (Map.Entry e : LombokInternalAliasing.ALIASES.entrySet()) { + if (e.getKey().equals(typeReference)) return e.getValue(); + } return null; } return unqualifiedToQualifiedMap.get(typeReference); diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java index e2ba03b5..287a085f 100644 --- a/src/core/lombok/core/TypeResolver.java +++ b/src/core/lombok/core/TypeResolver.java @@ -39,19 +39,12 @@ public class TypeResolver { this.imports = importList; } -// private static ImportList makeImportList(String packageString, Collection importStrings) { -// Set imports = new HashSet(); -// if (packageString != null) imports.add(packageString + ".*"); -// imports.addAll(importStrings == null ? Collections.emptySet() : importStrings); -// imports.add("java.lang.*"); -// return imports; -// } -// public boolean typeMatches(LombokNode context, String fqn, String typeRef) { return typeRefToFullyQualifiedName(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef) != null; } public String typeRefToFullyQualifiedName(LombokNode context, TypeLibrary library, String typeRef) { + typeRef = LombokInternalAliasing.processAliases(typeRef); // When asking if 'Foo' could possibly be referring to 'bar.Baz', the answer is obviously no. String qualified = library.toQualified(typeRef); if (qualified == null) return null; diff --git a/src/core/lombok/eclipse/EclipseImportList.java b/src/core/lombok/eclipse/EclipseImportList.java index 264ed91f..69246b3c 100644 --- a/src/core/lombok/eclipse/EclipseImportList.java +++ b/src/core/lombok/eclipse/EclipseImportList.java @@ -21,19 +21,21 @@ */ package lombok.eclipse; -import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.Eclipse.toQualifiedName; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ImportReference; -import lombok.core.ImportList; - public class EclipseImportList implements ImportList { private ImportReference[] imports; private ImportReference pkg; @@ -53,13 +55,16 @@ public class EclipseImportList implements ImportList { int len = token.length; if (len != unqualified.length()) continue; for (int i = 0; i < len; i++) if (token[i] != unqualified.charAt(i)) continue outer; - return toQualifiedName(tokens); + return LombokInternalAliasing.processAliases(toQualifiedName(tokens)); } } return null; } @Override public boolean hasStarImport(String packageName) { + for (Map.Entry e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } if (isEqual(packageName, pkg)) return true; if ("java.lang".equals(packageName)) return true; if (imports != null) for (ImportReference imp : imports) { diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index 60938649..0607137b 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -32,7 +32,7 @@ import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; diff --git a/src/core/lombok/javac/JavacImportList.java b/src/core/lombok/javac/JavacImportList.java index fbd4a518..d5d7460a 100644 --- a/src/core/lombok/javac/JavacImportList.java +++ b/src/core/lombok/javac/JavacImportList.java @@ -23,6 +23,7 @@ package lombok.javac; import java.util.ArrayList; import java.util.Collection; +import java.util.Map; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -32,6 +33,7 @@ import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.util.List; import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; public class JavacImportList implements ImportList { private final JCExpression pkg; @@ -48,13 +50,18 @@ public class JavacImportList implements ImportList { JCTree qual = ((JCImport) def).qualid; if (!(qual instanceof JCFieldAccess)) continue; String simpleName = ((JCFieldAccess) qual).name.toString(); - if (simpleName.equals(unqualified)) return qual.toString(); + if (simpleName.equals(unqualified)) { + return LombokInternalAliasing.processAliases(qual.toString()); + } } return null; } @Override public boolean hasStarImport(String packageName) { + for (Map.Entry e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } if (pkg != null && pkg.toString().equals(packageName)) return true; if ("java.lang".equals(packageName)) return true; diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index a59865f7..c0127f3c 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -26,7 +26,7 @@ import lombok.AccessLevel; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; @@ -45,7 +45,7 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { - deleteAnnotationIfNeccessary(annotationNode, Value.class); + deleteAnnotationIfNeccessary(annotationNode, Value.class, lombok.experimental.Value.class); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 92cebf4c..1784be90 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -280,7 +280,22 @@ public class JavacHandlerUtil { * then removes any import statement that imports this exact annotation (not star imports). * Only does this if the DeleteLombokAnnotations class is in the context. */ + @SuppressWarnings("unchecked") public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType) { + deleteAnnotationIfNeccessary0(annotation, annotationType); + } + + /** + * Removes the annotation from javac's AST (it remains in lombok's AST), + * then removes any import statement that imports this exact annotation (not star imports). + * Only does this if the DeleteLombokAnnotations class is in the context. + */ + @SuppressWarnings("unchecked") + public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType1, Class annotationType2) { + deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2); + } + + private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class... annotationTypes) { if (inNetbeansEditor(annotation)) return; if (!annotation.shouldDeleteLombokAnnotations()) return; JavacNode parentNode = annotation.directUp(); @@ -309,7 +324,9 @@ public class JavacHandlerUtil { } parentNode.getAst().setChanged(); - deleteImportFromCompilationUnit(annotation, annotationType.getName()); + for (Class annotationType : annotationTypes) { + deleteImportFromCompilationUnit(annotation, annotationType.getName()); + } } public static void deleteImportFromCompilationUnit(JavacNode node, String name) { diff --git a/src/delombok/lombok/delombok/DelombokApp.java b/src/delombok/lombok/delombok/DelombokApp.java index 90a7b55e..5b97be08 100644 --- a/src/delombok/lombok/delombok/DelombokApp.java +++ b/src/delombok/lombok/delombok/DelombokApp.java @@ -84,6 +84,7 @@ public class DelombokApp extends LombokApp { return null; } + @SuppressWarnings("resource") final JarFile toolsJarFile = new JarFile(toolsJar); ClassLoader loader = new ClassLoader() { diff --git a/test/transform/resource/after-delombok/ValueExperimental.java b/test/transform/resource/after-delombok/ValueExperimental.java new file mode 100644 index 00000000..77a48ec9 --- /dev/null +++ b/test/transform/resource/after-delombok/ValueExperimental.java @@ -0,0 +1,46 @@ +final class ValueExperimental1 { + @java.lang.SuppressWarnings("all") + public ValueExperimental1() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimental1)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimental1()"; + } +} +final class ValueExperimental2 { + @java.lang.SuppressWarnings("all") + public ValueExperimental2() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimental2)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimental2()"; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValueExperimentalStarImport.java b/test/transform/resource/after-delombok/ValueExperimentalStarImport.java new file mode 100644 index 00000000..6911f260 --- /dev/null +++ b/test/transform/resource/after-delombok/ValueExperimentalStarImport.java @@ -0,0 +1,25 @@ +import lombok.experimental.*; +final class ValueExperimentalStarImport { + @java.lang.SuppressWarnings("all") + public ValueExperimentalStarImport() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimentalStarImport)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimentalStarImport()"; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/ValueExperimental.java b/test/transform/resource/after-ecj/ValueExperimental.java new file mode 100644 index 00000000..dd13574a --- /dev/null +++ b/test/transform/resource/after-ecj/ValueExperimental.java @@ -0,0 +1,39 @@ +import lombok.experimental.Value; +final @Value class ValueExperimental1 { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimental1))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimental1()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimental1() { + super(); + } +} +final @lombok.experimental.Value class ValueExperimental2 { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimental2))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimental2()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimental2() { + super(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/ValueExperimentalStarImport.java b/test/transform/resource/after-ecj/ValueExperimentalStarImport.java new file mode 100644 index 00000000..b69e85d9 --- /dev/null +++ b/test/transform/resource/after-ecj/ValueExperimentalStarImport.java @@ -0,0 +1,20 @@ +import lombok.experimental.*; +final @Value class ValueExperimentalStarImport { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimentalStarImport))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimentalStarImport()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimentalStarImport() { + super(); + } +} diff --git a/test/transform/resource/after-ecj/ValuePlain.java b/test/transform/resource/after-ecj/ValuePlain.java index b798b308..d095913f 100644 --- a/test/transform/resource/after-ecj/ValuePlain.java +++ b/test/transform/resource/after-ecj/ValuePlain.java @@ -1,5 +1,5 @@ -import lombok.experimental.Value; -final @lombok.experimental.Value class Value1 { +import lombok.Value; +final @lombok.Value class Value1 { private final int x; private final String name; public @java.lang.SuppressWarnings("all") int getX() { diff --git a/test/transform/resource/before/ValueExperimental.java b/test/transform/resource/before/ValueExperimental.java new file mode 100644 index 00000000..6bae26a0 --- /dev/null +++ b/test/transform/resource/before/ValueExperimental.java @@ -0,0 +1,9 @@ +import lombok.experimental.Value; + +@Value +class ValueExperimental1 { +} + +@lombok.experimental.Value +class ValueExperimental2 { +} \ No newline at end of file diff --git a/test/transform/resource/before/ValueExperimentalStarImport.java b/test/transform/resource/before/ValueExperimentalStarImport.java new file mode 100644 index 00000000..5f18cffe --- /dev/null +++ b/test/transform/resource/before/ValueExperimentalStarImport.java @@ -0,0 +1,5 @@ +import lombok.experimental.*; + +@Value +class ValueExperimentalStarImport { +} \ No newline at end of file diff --git a/test/transform/resource/before/ValuePlain.java b/test/transform/resource/before/ValuePlain.java index 39c583cc..3fe33705 100644 --- a/test/transform/resource/before/ValuePlain.java +++ b/test/transform/resource/before/ValuePlain.java @@ -1,5 +1,5 @@ -import lombok.experimental.Value; -@lombok.experimental.Value class Value1 { +import lombok.Value; +@lombok.Value class Value1 { final int x; String name; } diff --git a/website/features/Data.html b/website/features/Data.html index 1c8510b7..ad3aa892 100644 --- a/website/features/Data.html +++ b/website/features/Data.html @@ -75,7 +75,7 @@
diff --git a/website/features/SneakyThrows.html b/website/features/SneakyThrows.html index 573bd95c..3b3987e4 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -70,7 +70,7 @@
diff --git a/website/features/Value.html b/website/features/Value.html new file mode 100644 index 00000000..92fcc825 --- /dev/null +++ b/website/features/Value.html @@ -0,0 +1,76 @@ + + + + + + + + @ExtensionMethod +
+
+
+ +

@Value

+ +
+

Since

+

+ @Value was introduced as experimental feature in lombok v0.11.4. +

+ @Value no longer implies @Wither since lombok v0.11.8. +

+ @Value promoted to the main lombok package since lombok v0.11.10. +

+
+

Overview

+

+ @Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass. Like @Data, useful toString(), equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every + argument (except final fields that are initialized in the field declaration) is also generated. +

+ In practice, @Value is shorthand for: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter. +

+ It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
+ It is possible to override any default behaviour for any of the 'parts' that make up @Value by explicitly using that annotation. +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Look for the documentation on the 'parts' of @Value: @ToString, @EqualsAndHashCode, @AllArgsConstructor, @FieldDefaults, and @Getter. +

+ For classes with generics, it's useful to have a static method which serves as a constructor, because inference of generic parameters via static methods works in java6 and avoids having to use the diamond operator. While you can force this by applying an explicit @AllArgsConstructor(staticConstructor="of") annotation, there's also the @Value(staticConstructor="of") feature, which will make the generated all-arguments constructor private, and generates a public static method named of which is a wrapper around this private constructor. +

+ @Value was an experimental feature from v0.11.4 to v0.11.9 (as @lombok.experimental.Value). It has since been moved into the core package. The old annotation is still + around (and is an alias). It will eventually be removed in a future version, though. +

+
+ +
+
+
+ + + diff --git a/website/features/experimental/Value.html b/website/features/experimental/Value.html deleted file mode 100644 index d2acfee4..00000000 --- a/website/features/experimental/Value.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - EXPERIMENTAL - @ExtensionMethod -
-
-
- -

@Value

- -
-

Since

-

- @Value was introduced as experimental feature in lombok v0.11.4. -

- @Value no longer implies @Wither since lombok v0.11.8. -

-
-

Experimental

-

- Experimental because: -

    -
  • Various choices still have to be vetted as being the correct 'least surprise' choice: Should the class be made final by default, etc.
  • -
- Current status: positive - Currently we feel this feature may move out of experimental status with no or minor changes soon. -
-
-

Overview

-

- @Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass. Like @Data, useful toString(), equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every - argument (except final fields that are initialized in the field declaration) is also generated. -

- In practice, @Value is shorthand for: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter. -

- It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
- It is possible to override any default behaviour for any of the 'parts' that make up @Value by explicitly using that annotation. -

-
-
-
-

With Lombok

-
@HTML_PRE@
-
-
-
-

Vanilla Java

-
@HTML_POST@
-
-
-
-
-

Small print

-

- Look for the documentation on the 'parts' of @Value: @ToString, @EqualsAndHashCode, @AllArgsConstructor, @FieldDefaults, and @Getter. -

- For classes with generics, it's useful to have a static method which serves as a constructor, because inference of generic parameters via static methods works in java6 and avoids having to use the diamond operator. While you can force this by applying an explicit @AllArgsConstructor(staticConstructor="of") annotation, there's also the @Value(staticConstructor="of") feature, which will make the generated all-arguments constructor private, and generates a public static method named of which is a wrapper around this private constructor. -

-
-
- -
-
-
- - - diff --git a/website/features/experimental/Wither.html b/website/features/experimental/Wither.html index da2156cd..9cbcd5ed 100644 --- a/website/features/experimental/Wither.html +++ b/website/features/experimental/Wither.html @@ -83,7 +83,7 @@
diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index d0a086a0..31fcd5ad 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -32,12 +32,17 @@
New default field modifiers for the 21st century.
@Wither
Immutable 'setters' - methods that create a clone but with one changed field.
-
@Value
-
Immutable classes made very easy.
onMethod= / onConstructor= / onParam
Sup dawg, we heard you like annotations, so we put annotations in your annotations so you can annotate while you're annotating.
+
+

Putting the "Ex" in "Experimental": promoted or deleted experimental features.

+
+
@Value: Promoted
+
@Value has proven its value and has been moved to the main package. +
+
diff --git a/website/features/experimental/onX.html b/website/features/experimental/onX.html index 99365deb..66b0164f 100644 --- a/website/features/experimental/onX.html +++ b/website/features/experimental/onX.html @@ -69,7 +69,7 @@
diff --git a/website/features/index.html b/website/features/index.html index 8b3765c1..f9b8cdfa 100644 --- a/website/features/index.html +++ b/website/features/index.html @@ -30,6 +30,8 @@
@Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
+
@Value
+
Immutable classes made very easy.
@SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
@Synchronized
-- cgit From 54ef2d08523abf09d8be127e59bcf61106479ca6 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:27:09 +0200 Subject: ... and now that @Value has been promoted to the main package, the old experimental one is now deprecated. --- src/core/lombok/experimental/Value.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index 048066df..8c78143c 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -42,6 +42,7 @@ import java.lang.annotation.Target; */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) +@Deprecated public @interface Value { /** * If you specify a static constructor name, then the generated constructor will be private, and -- cgit From c3e4eb4f745b9f11b07f4a2c48273e7dfdeb7f69 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:28:44 +0200 Subject: and added some more javadoc to point at the new main package variant --- src/core/lombok/experimental/Value.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index 8c78143c..b7700bb5 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -39,6 +39,7 @@ import java.lang.annotation.Target; * @see lombok.ToString * @see lombok.EqualsAndHashCode * @see lombok.Data + * @deprecated {@link lombok.Value} has been promoted to the main package, so use that one instead. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) -- cgit From fb32de51afaef2e2a3135b7230f47add005bae69 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:29:57 +0200 Subject: And now the changelogs are also updated to reflect that Value has been promoted. Racking up the commits here... --- doc/changelog.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 95ee5764..e922f8c9 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) -- cgit From 420e0dcb53654f41d0f79b004d6c750414e6ba7a Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 13:03:42 +0200 Subject: deprecating the old Value annotation also added deprecation warnings to where we still support it as an alias. fixed. --- src/core/lombok/javac/handlers/HandleValue.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index c0127f3c..15fb4781 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -22,6 +22,9 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.lang.annotation.Annotation; + import lombok.AccessLevel; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; @@ -45,7 +48,9 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { - deleteAnnotationIfNeccessary(annotationNode, Value.class, lombok.experimental.Value.class); + @SuppressWarnings("deprecation") + Class oldExperimentalValue = lombok.experimental.Value.class; + deleteAnnotationIfNeccessary(annotationNode, Value.class, oldExperimentalValue); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); -- cgit From a8fe76af4df9f17257e3923f895bc06ec68efe4d Mon Sep 17 00:00:00 2001 From: Martin Goldhahn Date: Fri, 28 Jun 2013 13:58:36 +0200 Subject: allow installer to be started with lombok.installer.fullpath, that creates proper paths on Windows --- src/installer/lombok/installer/IdeLocation.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/installer/lombok/installer/IdeLocation.java b/src/installer/lombok/installer/IdeLocation.java index a15f2ac9..4d28fb90 100644 --- a/src/installer/lombok/installer/IdeLocation.java +++ b/src/installer/lombok/installer/IdeLocation.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import lombok.installer.eclipse.EclipseFinder; import lombok.patcher.inject.LiveInjector; /** @@ -66,11 +67,12 @@ public abstract class IdeLocation { } private static final String LEGAL_PATH_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/"; + private static final String LEGAL_PATH_CHARS_WINDOWS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/:\\ "; public static String escapePath(String path) { StringBuilder out = new StringBuilder(); - + String legalChars = IdeFinder.getOS() == EclipseFinder.OS.UNIX ? LEGAL_PATH_CHARS : LEGAL_PATH_CHARS_WINDOWS; for (char c : path.toCharArray()) { - if (LEGAL_PATH_CHARS.indexOf(c) == -1) out.append('\\'); + if (legalChars.indexOf(c) == -1) out.append('\\'); out.append(c); } return out.toString(); -- cgit From 446a8e33e00cb9effe1d1e181cac192a70648412 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 06:33:18 +0200 Subject: FINALLY! Found the cause of a really weird eclipse bug, where _ANY_ mention of com.sun.tools.javac.tree.TreeMaker, anywhere in a source file, would disable pretty much every intelligent part of what makes the 'I' in IDE in eclipse: No auto-complete, no 'go to declaration', etcetera, but only since Eclipse Juno (not fixed in Kepler either). It's the presence of src/stubs/com/sun/tools/javac/util/Context.java. I've moved Context to a special stubs directory that's only used for javac (so that we still get the benefit of getting some warnings and such when making command line builds), and removed the @Override annotations for where the stubbing is relevant (for methods that exist in javac7 but not in javac6 on interfaces we create implementations of). Furthermore, I did some extremely tricky work in making our version actuall compatible with the exact class signatures of both javac6- and javac7+'s versions; generation of synthetic methods for reified type parameters was causing havoc. A big stack of 'here be voodoo' comments unfortunately added to explain it all; necessary evil. --- build.xml | 1 + .../com/sun/tools/javac/util/Context.java | 31 ++++++++++++++++++++++ src/stubs/com/sun/tools/javac/util/Context.java | 31 ---------------------- .../java6/CommentCollectingScannerFactory.java | 27 ++++++++++++++++--- .../java7/CommentCollectingScannerFactory.java | 29 +++++++++++++++++--- 5 files changed, 80 insertions(+), 39 deletions(-) create mode 100644 src/javac-only-stubs/com/sun/tools/javac/util/Context.java delete mode 100644 src/stubs/com/sun/tools/javac/util/Context.java diff --git a/build.xml b/build.xml index 9ebbe9e7..bf13eeff 100644 --- a/build.xml +++ b/build.xml @@ -147,6 +147,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr + diff --git a/src/javac-only-stubs/com/sun/tools/javac/util/Context.java b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java new file mode 100644 index 00000000..06b8ff4d --- /dev/null +++ b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java @@ -0,0 +1,31 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.util; + +public class Context { + public static class Key { + } + + public interface Factory { + T make(Context c); + T make(); + } + + public void put(Key key, Factory fac) { + } + + public void put(Key key, T data) { + } + + public void put(Class clazz, T data) { + } + + public T get(Key key) { + return null; + } + + public T get(Class clazz) { + return null; + } +} diff --git a/src/stubs/com/sun/tools/javac/util/Context.java b/src/stubs/com/sun/tools/javac/util/Context.java deleted file mode 100644 index 06b8ff4d..00000000 --- a/src/stubs/com/sun/tools/javac/util/Context.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. - */ -package com.sun.tools.javac.util; - -public class Context { - public static class Key { - } - - public interface Factory { - T make(Context c); - T make(); - } - - public void put(Key key, Factory fac) { - } - - public void put(Key key, T data) { - } - - public void put(Class clazz, T data) { - } - - public T get(Key key) { - return null; - } - - public T get(Class clazz) { - return null; - } -} diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index 30acbd5a..c345526e 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,14 +28,33 @@ import com.sun.tools.javac.util.Context; public class CommentCollectingScannerFactory extends Scanner.Factory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory() { - public CommentCollectingScanner.Factory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + context.put(scannerFactoryKey, new Context.Factory() { + public Object make() { return new CommentCollectingScannerFactory(context); } - public CommentCollectingScanner.Factory make(Context c) { + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } }); diff --git a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java index 9a29528e..2032e494 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,14 +29,35 @@ import com.sun.tools.javac.util.Context; public class CommentCollectingScannerFactory extends ScannerFactory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory() { - public ScannerFactory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + context.put(scannerFactoryKey, new Context.Factory() { + // This overrides the javac6- version of make. + public Object make() { return new CommentCollectingScannerFactory(context); } - @Override public ScannerFactory make(Context c) { + // This overrides the javac7+ version. + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } }); -- cgit From dfc682a0010ff1ba77496dd7644a6dcbe93017e5 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 09:48:58 +0200 Subject: lombok's compile target is 1.6, but, eclipse project was generated as 1.7. This caused a bunch of weirdness because eclipse changes a bunch of warnings and quickfixes based on this (such as complaining about lack of @SafeVarargs, a 1.7-only feature). --- build.xml | 2 +- src/delombok/lombok/delombok/DelombokApp.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.xml b/build.xml index bf13eeff..fb328e3e 100644 --- a/build.xml +++ b/build.xml @@ -256,7 +256,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr - + diff --git a/src/delombok/lombok/delombok/DelombokApp.java b/src/delombok/lombok/delombok/DelombokApp.java index 5b97be08..90a7b55e 100644 --- a/src/delombok/lombok/delombok/DelombokApp.java +++ b/src/delombok/lombok/delombok/DelombokApp.java @@ -84,7 +84,6 @@ public class DelombokApp extends LombokApp { return null; } - @SuppressWarnings("resource") final JarFile toolsJarFile = new JarFile(toolsJar); ClassLoader loader = new ClassLoader() { -- cgit From b6a23a7d94ee4f01b38a0e6d7db120fb05be0d6c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 09:14:59 +0200 Subject: @Getter(lazy=true) now uses a different, more efficient desugaring. Implementing in javac, ecj still has to be done. --- src/core/lombok/javac/handlers/HandleGetter.java | 72 +++++++++++++++------ .../resource/after-delombok/DelegateOnGetter.java | 8 +-- .../resource/after-delombok/GetterLazy.java | 8 +-- .../resource/after-delombok/GetterLazyBoolean.java | 16 ++--- .../after-delombok/GetterLazyEahcToString.java | 8 +-- .../resource/after-delombok/GetterLazyNative.java | 74 +++++++++++----------- .../messages-ecj/ValueExperimental.java.messages | 1 + 7 files changed, 109 insertions(+), 78 deletions(-) create mode 100644 test/transform/resource/messages-ecj/ValueExperimental.java.messages diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index bc68d5ad..0385a235 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -52,11 +52,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSynchronized; -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.tree.TreeMaker; @@ -286,6 +284,7 @@ public class HandleGetter extends JavacAnnotationHandler { } private static final String AR = "java.util.concurrent.atomic.AtomicReference"; + private static final String JLO = "java.lang.Object"; private static final List NIL_EXPRESSION = List.nil(); private static final java.util.Map TYPE_MAP; @@ -304,37 +303,50 @@ public class HandleGetter extends JavacAnnotationHandler { private List createLazyGetterBody(TreeMaker maker, JavacNode fieldNode, JCTree source) { /* - java.util.concurrent.atomic.AtomicReference value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); - if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + if (value == null) { + RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ ListBuffer statements = ListBuffer.lb(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression copyOfRawFieldType = copyType(maker, field); + JCExpression copyOfBoxedFieldType = null; field.type = null; + boolean isPrimitive = false; if (field.vartype instanceof JCPrimitiveTypeTree) { String boxed = TYPE_MAP.get(((JCPrimitiveTypeTree)field.vartype).typetag); if (boxed != null) { + isPrimitive = true; field.vartype = chainDotsString(fieldNode, boxed); + copyOfBoxedFieldType = chainDotsString(fieldNode, boxed); } } + if (copyOfBoxedFieldType == null) copyOfBoxedFieldType = copyType(maker, field); Name valueName = fieldNode.toName("value"); Name actualValueName = fieldNode.toName("actualValue"); - /* java.util.concurrent.atomic.AtomicReference value = this.fieldName.get();*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); + /* java.lang.Object value = this.fieldName.get();*/ { + JCExpression valueVarType = chainDotsString(fieldNode, JLO); statements.append(maker.VarDef(maker.Modifiers(0), valueName, valueVarType, callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)))); } @@ -349,15 +361,23 @@ public class HandleGetter extends JavacAnnotationHandler { /* if (value == null) { */ { ListBuffer innerIfStatements = ListBuffer.lb(); - /* ValueType actualValue = new ValueType(); */ { + /* RawValueType actualValue = INITIALIZER_EXPRESSION; */ { innerIfStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), actualValueName, copyOfRawFieldType, field.init)); } - /* value = new java.util.concurrent.atomic.AtomicReference(actualValue);*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); - JCNewClass newInstance = maker.NewClass(null, NIL_EXPRESSION, valueVarType, List.of(maker.Ident(actualValueName)), null); - - JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), newInstance)); - innerIfStatements.append(statement); + /* [IF primitive] value = actualValue; */ { + if (isPrimitive) { + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), maker.Ident(actualValueName))); + innerIfStatements.append(statement); + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + JCExpression actualValueIsNull = maker.Binary(CTC_EQUAL, maker.Ident(actualValueName), maker.Literal(CTC_BOT, null)); + JCExpression thisDotFieldName = createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD); + JCExpression ternary = maker.Conditional(actualValueIsNull, thisDotFieldName, maker.Ident(actualValueName)); + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), ternary)); + innerIfStatements.append(statement); + } } /* this.fieldName.set(value); */ { JCStatement statement = callSet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Ident(valueName)); @@ -376,15 +396,25 @@ public class HandleGetter extends JavacAnnotationHandler { JCIf ifStatement = maker.If(isNull, maker.Block(0, List.of(synchronizedStatement)), null); statements.append(ifStatement); } - /* return value.get(); */ - statements.append(maker.Return(callGet(fieldNode, maker.Ident(valueName)))); + /* [IF PRIMITIVE] return (BoxedValueType) value; */ { + if (isPrimitive) { + statements.append(maker.Return(maker.TypeCast(copyOfBoxedFieldType, maker.Ident(valueName)))); + } + } + /* [ELSE] return (BoxedValueType) (value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + JCExpression valueEqualsSelf = maker.Binary(CTC_EQUAL, maker.Ident(valueName), createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)); + JCExpression ternary = maker.Conditional(valueEqualsSelf, maker.Literal(CTC_BOT, null), maker.Ident(valueName)); + JCExpression typeCast = maker.TypeCast(copyOfBoxedFieldType, maker.Parens(ternary)); + statements.append(maker.Return(typeCast)); + } + } // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference>(); */ { + /* private final java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference(); */ { field.vartype = recursiveSetGeneratedBy( - maker.TypeApply(chainDotsString(fieldNode, AR), List.of(maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))))), - source); + maker.TypeApply(chainDotsString(fieldNode, AR), List.of(chainDotsString(fieldNode, JLO))), source); field.init = recursiveSetGeneratedBy(maker.NewClass(null, NIL_EXPRESSION, copyType(maker, field), NIL_EXPRESSION, null), source); } diff --git a/test/transform/resource/after-delombok/DelegateOnGetter.java b/test/transform/resource/after-delombok/DelegateOnGetter.java index 08d682a2..e8d01a49 100644 --- a/test/transform/resource/after-delombok/DelegateOnGetter.java +++ b/test/transform/resource/after-delombok/DelegateOnGetter.java @@ -1,12 +1,12 @@ class DelegateOnGetter { - private final java.util.concurrent.atomic.AtomicReference> bar = new java.util.concurrent.atomic.AtomicReference>(); + private final java.util.concurrent.atomic.AtomicReference bar = new java.util.concurrent.atomic.AtomicReference(); private interface Bar { void setList(java.util.ArrayList list); int getInt(); } @java.lang.SuppressWarnings("all") public Bar getBar() { - java.util.concurrent.atomic.AtomicReference value = this.bar.get(); + java.lang.Object value = this.bar.get(); if (value == null) { synchronized (this.bar) { value = this.bar.get(); @@ -18,12 +18,12 @@ class DelegateOnGetter { return 42; } }; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue == null ? this.bar : actualValue; this.bar.set(value); } } } - return value.get(); + return (Bar)(value == this.bar ? null : value); } @java.lang.SuppressWarnings("all") public void setList(final java.util.ArrayList list) { diff --git a/test/transform/resource/after-delombok/GetterLazy.java b/test/transform/resource/after-delombok/GetterLazy.java index 95be39e3..4f6f2c03 100644 --- a/test/transform/resource/after-delombok/GetterLazy.java +++ b/test/transform/resource/after-delombok/GetterLazy.java @@ -1,20 +1,20 @@ class GetterLazy { static class ValueType { } - private final java.util.concurrent.atomic.AtomicReference> fieldName = new java.util.concurrent.atomic.AtomicReference>(); + private final java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference(); @java.lang.SuppressWarnings("all") public ValueType getFieldName() { - java.util.concurrent.atomic.AtomicReference value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue == null ? this.fieldName : actualValue; this.fieldName.set(value); } } } - return value.get(); + return (ValueType)(value == this.fieldName ? null : value); } } diff --git a/test/transform/resource/after-delombok/GetterLazyBoolean.java b/test/transform/resource/after-delombok/GetterLazyBoolean.java index caab2803..08d32013 100644 --- a/test/transform/resource/after-delombok/GetterLazyBoolean.java +++ b/test/transform/resource/after-delombok/GetterLazyBoolean.java @@ -1,6 +1,6 @@ class GetterLazyBoolean { - private final java.util.concurrent.atomic.AtomicReference> booleanValue = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference>(); + private final java.util.concurrent.atomic.AtomicReference booleanValue = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference otherBooleanValue = new java.util.concurrent.atomic.AtomicReference(); private static boolean calculateBoolean() { return true; } @@ -33,32 +33,32 @@ class GetterLazyBoolean { } @java.lang.SuppressWarnings("all") public boolean isBooleanValue() { - java.util.concurrent.atomic.AtomicReference value = this.booleanValue.get(); + java.lang.Object value = this.booleanValue.get(); if (value == null) { synchronized (this.booleanValue) { value = this.booleanValue.get(); if (value == null) { final boolean actualValue = calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.booleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } @java.lang.SuppressWarnings("all") public boolean isOtherBooleanValue() { - java.util.concurrent.atomic.AtomicReference value = this.otherBooleanValue.get(); + java.lang.Object value = this.otherBooleanValue.get(); if (value == null) { synchronized (this.otherBooleanValue) { value = this.otherBooleanValue.get(); if (value == null) { final boolean actualValue = !calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.otherBooleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } } diff --git a/test/transform/resource/after-delombok/GetterLazyEahcToString.java b/test/transform/resource/after-delombok/GetterLazyEahcToString.java index ce3555fe..1358e536 100644 --- a/test/transform/resource/after-delombok/GetterLazyEahcToString.java +++ b/test/transform/resource/after-delombok/GetterLazyEahcToString.java @@ -1,6 +1,6 @@ class GetterLazyEahcToString { - private final java.util.concurrent.atomic.AtomicReference> value = new java.util.concurrent.atomic.AtomicReference>(); + private final java.util.concurrent.atomic.AtomicReference value = new java.util.concurrent.atomic.AtomicReference(); private final String value2 = ""; @java.lang.Override @@ -44,18 +44,18 @@ class GetterLazyEahcToString { @java.lang.SuppressWarnings("all") public String getValue() { - java.util.concurrent.atomic.AtomicReference value = this.value.get(); + java.lang.Object value = this.value.get(); if (value == null) { synchronized (this.value) { value = this.value.get(); if (value == null) { final String actualValue = ""; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue == null ? this.value : actualValue; this.value.set(value); } } } - return value.get(); + return (String)(value == this.value ? null : value); } @java.lang.SuppressWarnings("all") diff --git a/test/transform/resource/after-delombok/GetterLazyNative.java b/test/transform/resource/after-delombok/GetterLazyNative.java index a10075ba..eb31991a 100644 --- a/test/transform/resource/after-delombok/GetterLazyNative.java +++ b/test/transform/resource/after-delombok/GetterLazyNative.java @@ -1,146 +1,146 @@ class GetterLazyNative { - private final java.util.concurrent.atomic.AtomicReference> booleanField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> byteField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> shortField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> intField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> longField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> floatField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> doubleField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> charField = new java.util.concurrent.atomic.AtomicReference>(); - private final java.util.concurrent.atomic.AtomicReference> intArrayField = new java.util.concurrent.atomic.AtomicReference>(); + private final java.util.concurrent.atomic.AtomicReference booleanField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference byteField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference shortField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference intField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference longField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference floatField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference doubleField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference charField = new java.util.concurrent.atomic.AtomicReference(); + private final java.util.concurrent.atomic.AtomicReference intArrayField = new java.util.concurrent.atomic.AtomicReference(); @java.lang.SuppressWarnings("all") public boolean isBooleanField() { - java.util.concurrent.atomic.AtomicReference value = this.booleanField.get(); + java.lang.Object value = this.booleanField.get(); if (value == null) { synchronized (this.booleanField) { value = this.booleanField.get(); if (value == null) { final boolean actualValue = true; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.booleanField.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } @java.lang.SuppressWarnings("all") public byte getByteField() { - java.util.concurrent.atomic.AtomicReference value = this.byteField.get(); + java.lang.Object value = this.byteField.get(); if (value == null) { synchronized (this.byteField) { value = this.byteField.get(); if (value == null) { final byte actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.byteField.set(value); } } } - return value.get(); + return (java.lang.Byte)value; } @java.lang.SuppressWarnings("all") public short getShortField() { - java.util.concurrent.atomic.AtomicReference value = this.shortField.get(); + java.lang.Object value = this.shortField.get(); if (value == null) { synchronized (this.shortField) { value = this.shortField.get(); if (value == null) { final short actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.shortField.set(value); } } } - return value.get(); + return (java.lang.Short)value; } @java.lang.SuppressWarnings("all") public int getIntField() { - java.util.concurrent.atomic.AtomicReference value = this.intField.get(); + java.lang.Object value = this.intField.get(); if (value == null) { synchronized (this.intField) { value = this.intField.get(); if (value == null) { final int actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.intField.set(value); } } } - return value.get(); + return (java.lang.Integer)value; } @java.lang.SuppressWarnings("all") public long getLongField() { - java.util.concurrent.atomic.AtomicReference value = this.longField.get(); + java.lang.Object value = this.longField.get(); if (value == null) { synchronized (this.longField) { value = this.longField.get(); if (value == null) { final long actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.longField.set(value); } } } - return value.get(); + return (java.lang.Long)value; } @java.lang.SuppressWarnings("all") public float getFloatField() { - java.util.concurrent.atomic.AtomicReference value = this.floatField.get(); + java.lang.Object value = this.floatField.get(); if (value == null) { synchronized (this.floatField) { value = this.floatField.get(); if (value == null) { final float actualValue = 1.0F; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.floatField.set(value); } } } - return value.get(); + return (java.lang.Float)value; } @java.lang.SuppressWarnings("all") public double getDoubleField() { - java.util.concurrent.atomic.AtomicReference value = this.doubleField.get(); + java.lang.Object value = this.doubleField.get(); if (value == null) { synchronized (this.doubleField) { value = this.doubleField.get(); if (value == null) { final double actualValue = 1.0; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.doubleField.set(value); } } } - return value.get(); + return (java.lang.Double)value; } @java.lang.SuppressWarnings("all") public char getCharField() { - java.util.concurrent.atomic.AtomicReference value = this.charField.get(); + java.lang.Object value = this.charField.get(); if (value == null) { synchronized (this.charField) { value = this.charField.get(); if (value == null) { final char actualValue = '1'; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.charField.set(value); } } } - return value.get(); + return (java.lang.Character)value; } @java.lang.SuppressWarnings("all") public int[] getIntArrayField() { - java.util.concurrent.atomic.AtomicReference value = this.intArrayField.get(); + java.lang.Object value = this.intArrayField.get(); if (value == null) { synchronized (this.intArrayField) { value = this.intArrayField.get(); if (value == null) { final int[] actualValue = new int[]{1}; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue == null ? this.intArrayField : actualValue; this.intArrayField.set(value); } } } - return value.get(); + return (int[])(value == this.intArrayField ? null : value); } -} \ No newline at end of file +} diff --git a/test/transform/resource/messages-ecj/ValueExperimental.java.messages b/test/transform/resource/messages-ecj/ValueExperimental.java.messages new file mode 100644 index 00000000..db4520ca --- /dev/null +++ b/test/transform/resource/messages-ecj/ValueExperimental.java.messages @@ -0,0 +1 @@ +1:7 The type Value is deprecated \ No newline at end of file -- cgit From 85fec0dffa5df13c0cafc86ca762774ba2c4d951 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 21:41:47 +0200 Subject: eclipse support for the new @Getter(lazy=true) desugaring. --- src/core/lombok/eclipse/handlers/HandleGetter.java | 120 +++++++++++++-------- src/core/lombok/javac/handlers/HandleGetter.java | 4 +- .../resource/after-ecj/DelegateOnGetter.java | 8 +- test/transform/resource/after-ecj/GetterLazy.java | 8 +- .../resource/after-ecj/GetterLazyBoolean.java | 16 +-- .../resource/after-ecj/GetterLazyEahcToString.java | 8 +- .../resource/after-ecj/GetterLazyNative.java | 72 ++++++------- 7 files changed, 136 insertions(+), 100 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 7f788c5d..760c595e 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -49,6 +49,8 @@ import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.CastExpression; +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.FieldDeclaration; @@ -67,6 +69,7 @@ import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; 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.TypeConstants; import org.mangosdk.spi.ProviderFor; /** @@ -286,7 +289,6 @@ public class HandleGetter extends EclipseAnnotationHandler { } private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference"); - private static final TypeReference[][] AR_PARAMS = new TypeReference[5][]; private static final java.util.Map TYPE_MAP; static { @@ -305,41 +307,54 @@ public class HandleGetter extends EclipseAnnotationHandler { private static char[] valueName = "value".toCharArray(); private static char[] actualValueName = "actualValue".toCharArray(); + private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK; + private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) { /* - java.util.concurrent.atomic.AtomicReference value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeReference componentType = copyType(field.type, source); + TypeReference rawComponentType = copyType(field.type, source); + TypeReference boxedComponentType = null; + boolean isPrimitive = false; if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) { char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token)); if (newType != null) { - componentType = new QualifiedTypeReference(newType, poss(source, 3)); + boxedComponentType = new QualifiedTypeReference(newType, poss(source, 3)); + isPrimitive = true; } } + if (boxedComponentType == null) boxedComponentType = copyType(field.type, source); + boxedComponentType.sourceStart = pS; boxedComponentType.sourceEnd = boxedComponentType.statementEnd = pE; Statement[] statements = new Statement[3]; - /* java.util.concurrent.atomic.AtomicReference value = this.fieldName.get(); */ { + /* java.lang.Object value = this.fieldName.get(); */ { LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE); - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + valueDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = valueDecl.type.statementEnd = pE; MessageSend getter = new MessageSend(); @@ -356,8 +371,12 @@ public class HandleGetter extends EclipseAnnotationHandler { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + final ValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } @@ -383,28 +402,37 @@ public class HandleGetter extends EclipseAnnotationHandler { EqualExpression innerCond = new EqualExpression( new SingleNameReference(valueName, p), new NullLiteral(pS, pE), BinaryExpression.EQUAL_EQUAL); + innerCond.sourceStart = pS; innerCond.sourceEnd = innerCond.statementEnd = pE; Block innerThen = new Block(0); innerThen.statements = new Statement[3]; - /* final ValueType actualValue = new ValueType(); */ { + /* final ValueType actualValue = INITIALIZER_EXPRESSION */ { LocalDeclaration actualValueDecl = new LocalDeclaration(actualValueName, pS, pE); - actualValueDecl.type = copyType(field.type, source); + actualValueDecl.type = rawComponentType; actualValueDecl.type.sourceStart = pS; actualValueDecl.type.sourceEnd = actualValueDecl.type.statementEnd = pE; actualValueDecl.initialization = field.initialization; actualValueDecl.modifiers = ClassFileConstants.AccFinal; innerThen.statements[0] = actualValueDecl; } - /* value = new java.util.concurrent.atomic.AtomicReference(actualValue); */ { - AllocationExpression create = new AllocationExpression(); - create.sourceStart = pS; create.sourceEnd = create.statementEnd = pE; - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); - create.type.sourceStart = pS; create.type.sourceEnd = create.type.statementEnd = pE; - create.arguments = new Expression[] {new SingleNameReference(actualValueName, p)}; - Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE); - innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; - - innerThen.statements[1] = innerAssign; + /* [IF PRIMITIVE] value = actualValue; */ { + if (isPrimitive) { + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), new SingleNameReference(actualValueName, p), pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + EqualExpression avIsNull = new EqualExpression( + new SingleNameReference(actualValueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + avIsNull.sourceStart = pS; avIsNull.sourceEnd = avIsNull.statementEnd = pE; + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + ConditionalExpression ternary = new ConditionalExpression(avIsNull, fieldRef, new SingleNameReference(actualValueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), ternary, pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } } /* this.fieldName.set(value); */ { @@ -428,26 +456,34 @@ public class HandleGetter extends EclipseAnnotationHandler { statements[1] = ifStatement; } - /* return value.get(); */ { - MessageSend getter = new MessageSend(); - getter.sourceStart = pS; getter.sourceEnd = getter.statementEnd = pE; - getter.selector = new char[] {'g', 'e', 't'}; - getter.receiver = new SingleNameReference(valueName, p); - - statements[2] = new ReturnStatement(getter, pS, pE); + /* [IF PRIMITIVE] return (BoxedValueType)value; */ { + if (isPrimitive) { + CastExpression cast = makeCastExpression(new SingleNameReference(valueName, p), boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } + } + /* [ELSE] return (BoxedValueType)(value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + EqualExpression vIsThisFieldName = new EqualExpression( + new SingleNameReference(valueName, p), createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + BinaryExpression.EQUAL_EQUAL); + vIsThisFieldName.sourceStart = pS; vIsThisFieldName.sourceEnd = vIsThisFieldName.statementEnd = pE; + ConditionalExpression ternary = new ConditionalExpression(vIsThisFieldName, new NullLiteral(pS, pE), new SingleNameReference(valueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + ternary.bits |= PARENTHESIZED; + CastExpression cast = makeCastExpression(ternary, boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } } - // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference>(); */ { - - LocalDeclaration first = (LocalDeclaration) statements[0]; - TypeReference innerType = copyType(first.type, source); - - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(innerType, source)}; + /* private final java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference(); */ { + TypeReference innerType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); + TypeReference[][] typeParams = new TypeReference[5][]; + typeParams[4] = new TypeReference[] {innerType}; TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + // Some magic here type.sourceStart = -1; type.sourceEnd = -2; diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 0385a235..413404c0 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -308,7 +308,7 @@ public class HandleGetter extends JavacAnnotationHandler { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - RawValueType actualValue = INITIALIZER_EXPRESSION; + final RawValueType actualValue = INITIALIZER_EXPRESSION; [IF PRIMITIVE] value = actualValue; [ELSE] @@ -361,7 +361,7 @@ public class HandleGetter extends JavacAnnotationHandler { /* if (value == null) { */ { ListBuffer innerIfStatements = ListBuffer.lb(); - /* RawValueType actualValue = INITIALIZER_EXPRESSION; */ { + /* final RawValueType actualValue = INITIALIZER_EXPRESSION; */ { innerIfStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), actualValueName, copyOfRawFieldType, field.init)); } /* [IF primitive] value = actualValue; */ { diff --git a/test/transform/resource/after-ecj/DelegateOnGetter.java b/test/transform/resource/after-ecj/DelegateOnGetter.java index 4eab3791..59f6a3b8 100644 --- a/test/transform/resource/after-ecj/DelegateOnGetter.java +++ b/test/transform/resource/after-ecj/DelegateOnGetter.java @@ -5,12 +5,12 @@ class DelegateOnGetter { void setList(java.util.ArrayList list); int getInt(); } - private final @Delegate @Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> bar = new java.util.concurrent.atomic.AtomicReference>(); + private final @Delegate @Getter(lazy = true) java.util.concurrent.atomic.AtomicReference bar = new java.util.concurrent.atomic.AtomicReference(); DelegateOnGetter() { super(); } public @Delegate @java.lang.SuppressWarnings("all") Bar getBar() { - java.util.concurrent.atomic.AtomicReference value = this.bar.get(); + java.lang.Object value = this.bar.get(); if ((value == null)) { synchronized (this.bar) @@ -28,12 +28,12 @@ class DelegateOnGetter { return 42; } }; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = ((actualValue == null) ? this.bar : actualValue); this.bar.set(value); } } } - return value.get(); + return (Bar) ((value == this.bar) ? null : value); } public @java.lang.SuppressWarnings("all") int getInt() { return this.getBar().getInt(); diff --git a/test/transform/resource/after-ecj/GetterLazy.java b/test/transform/resource/after-ecj/GetterLazy.java index 0f5027b9..8394f58d 100644 --- a/test/transform/resource/after-ecj/GetterLazy.java +++ b/test/transform/resource/after-ecj/GetterLazy.java @@ -4,12 +4,12 @@ class GetterLazy { super(); } } - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> fieldName = new java.util.concurrent.atomic.AtomicReference>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference fieldName = new java.util.concurrent.atomic.AtomicReference(); GetterLazy() { super(); } public @java.lang.SuppressWarnings("all") ValueType getFieldName() { - java.util.concurrent.atomic.AtomicReference value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if ((value == null)) { synchronized (this.fieldName) @@ -18,11 +18,11 @@ class GetterLazy { if ((value == null)) { final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = ((actualValue == null) ? this.fieldName : actualValue); this.fieldName.set(value); } } } - return value.get(); + return (ValueType) ((value == this.fieldName) ? null : value); } } diff --git a/test/transform/resource/after-ecj/GetterLazyBoolean.java b/test/transform/resource/after-ecj/GetterLazyBoolean.java index 8c890827..a51b164f 100644 --- a/test/transform/resource/after-ecj/GetterLazyBoolean.java +++ b/test/transform/resource/after-ecj/GetterLazyBoolean.java @@ -1,6 +1,6 @@ @lombok.EqualsAndHashCode(of = "booleanValue") @lombok.ToString(of = "booleanValue") class GetterLazyBoolean { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> booleanValue = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference booleanValue = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference otherBooleanValue = new java.util.concurrent.atomic.AtomicReference(); GetterLazyBoolean() { super(); } @@ -8,7 +8,7 @@ return true; } public @java.lang.SuppressWarnings("all") boolean isBooleanValue() { - java.util.concurrent.atomic.AtomicReference value = this.booleanValue.get(); + java.lang.Object value = this.booleanValue.get(); if ((value == null)) { synchronized (this.booleanValue) @@ -17,15 +17,15 @@ if ((value == null)) { final boolean actualValue = calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.booleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.SuppressWarnings("all") boolean isOtherBooleanValue() { - java.util.concurrent.atomic.AtomicReference value = this.otherBooleanValue.get(); + java.lang.Object value = this.otherBooleanValue.get(); if ((value == null)) { synchronized (this.otherBooleanValue) @@ -34,12 +34,12 @@ if ((value == null)) { final boolean actualValue = (! calculateBoolean()); - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.otherBooleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { if ((o == this)) diff --git a/test/transform/resource/after-ecj/GetterLazyEahcToString.java b/test/transform/resource/after-ecj/GetterLazyEahcToString.java index 1ca848af..db2d4fcf 100644 --- a/test/transform/resource/after-ecj/GetterLazyEahcToString.java +++ b/test/transform/resource/after-ecj/GetterLazyEahcToString.java @@ -1,11 +1,11 @@ @lombok.EqualsAndHashCode(doNotUseGetters = true) @lombok.ToString(doNotUseGetters = true) class GetterLazyEahcToString { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> value = new java.util.concurrent.atomic.AtomicReference>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference value = new java.util.concurrent.atomic.AtomicReference(); private final @lombok.Getter String value2 = ""; GetterLazyEahcToString() { super(); } public @java.lang.SuppressWarnings("all") String getValue() { - java.util.concurrent.atomic.AtomicReference value = this.value.get(); + java.lang.Object value = this.value.get(); if ((value == null)) { synchronized (this.value) @@ -14,12 +14,12 @@ if ((value == null)) { final String actualValue = ""; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = ((actualValue == null) ? this.value : actualValue); this.value.set(value); } } } - return value.get(); + return (String) ((value == this.value) ? null : value); } public @java.lang.SuppressWarnings("all") String getValue2() { return this.value2; diff --git a/test/transform/resource/after-ecj/GetterLazyNative.java b/test/transform/resource/after-ecj/GetterLazyNative.java index b67abfc9..db70f2f4 100644 --- a/test/transform/resource/after-ecj/GetterLazyNative.java +++ b/test/transform/resource/after-ecj/GetterLazyNative.java @@ -1,18 +1,18 @@ class GetterLazyNative { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> booleanField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> byteField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> shortField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> intField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> longField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> floatField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> doubleField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> charField = new java.util.concurrent.atomic.AtomicReference>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference> intArrayField = new java.util.concurrent.atomic.AtomicReference>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference booleanField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference byteField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference shortField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference intField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference longField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference floatField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference doubleField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference charField = new java.util.concurrent.atomic.AtomicReference(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference intArrayField = new java.util.concurrent.atomic.AtomicReference(); GetterLazyNative() { super(); } public @java.lang.SuppressWarnings("all") boolean isBooleanField() { - java.util.concurrent.atomic.AtomicReference value = this.booleanField.get(); + java.lang.Object value = this.booleanField.get(); if ((value == null)) { synchronized (this.booleanField) @@ -21,15 +21,15 @@ class GetterLazyNative { if ((value == null)) { final boolean actualValue = true; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.booleanField.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.SuppressWarnings("all") byte getByteField() { - java.util.concurrent.atomic.AtomicReference value = this.byteField.get(); + java.lang.Object value = this.byteField.get(); if ((value == null)) { synchronized (this.byteField) @@ -38,15 +38,15 @@ class GetterLazyNative { if ((value == null)) { final byte actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.byteField.set(value); } } } - return value.get(); + return (java.lang.Byte) value; } public @java.lang.SuppressWarnings("all") short getShortField() { - java.util.concurrent.atomic.AtomicReference value = this.shortField.get(); + java.lang.Object value = this.shortField.get(); if ((value == null)) { synchronized (this.shortField) @@ -55,15 +55,15 @@ class GetterLazyNative { if ((value == null)) { final short actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.shortField.set(value); } } } - return value.get(); + return (java.lang.Short) value; } public @java.lang.SuppressWarnings("all") int getIntField() { - java.util.concurrent.atomic.AtomicReference value = this.intField.get(); + java.lang.Object value = this.intField.get(); if ((value == null)) { synchronized (this.intField) @@ -72,15 +72,15 @@ class GetterLazyNative { if ((value == null)) { final int actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.intField.set(value); } } } - return value.get(); + return (java.lang.Integer) value; } public @java.lang.SuppressWarnings("all") long getLongField() { - java.util.concurrent.atomic.AtomicReference value = this.longField.get(); + java.lang.Object value = this.longField.get(); if ((value == null)) { synchronized (this.longField) @@ -89,15 +89,15 @@ class GetterLazyNative { if ((value == null)) { final long actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.longField.set(value); } } } - return value.get(); + return (java.lang.Long) value; } public @java.lang.SuppressWarnings("all") float getFloatField() { - java.util.concurrent.atomic.AtomicReference value = this.floatField.get(); + java.lang.Object value = this.floatField.get(); if ((value == null)) { synchronized (this.floatField) @@ -106,15 +106,15 @@ class GetterLazyNative { if ((value == null)) { final float actualValue = 1.0f; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.floatField.set(value); } } } - return value.get(); + return (java.lang.Float) value; } public @java.lang.SuppressWarnings("all") double getDoubleField() { - java.util.concurrent.atomic.AtomicReference value = this.doubleField.get(); + java.lang.Object value = this.doubleField.get(); if ((value == null)) { synchronized (this.doubleField) @@ -123,15 +123,15 @@ class GetterLazyNative { if ((value == null)) { final double actualValue = 1.0; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.doubleField.set(value); } } } - return value.get(); + return (java.lang.Double) value; } public @java.lang.SuppressWarnings("all") char getCharField() { - java.util.concurrent.atomic.AtomicReference value = this.charField.get(); + java.lang.Object value = this.charField.get(); if ((value == null)) { synchronized (this.charField) @@ -140,15 +140,15 @@ class GetterLazyNative { if ((value == null)) { final char actualValue = '1'; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = actualValue; this.charField.set(value); } } } - return value.get(); + return (java.lang.Character) value; } public @java.lang.SuppressWarnings("all") int[] getIntArrayField() { - java.util.concurrent.atomic.AtomicReference value = this.intArrayField.get(); + java.lang.Object value = this.intArrayField.get(); if ((value == null)) { synchronized (this.intArrayField) @@ -157,11 +157,11 @@ class GetterLazyNative { if ((value == null)) { final int[] actualValue = new int[]{1}; - value = new java.util.concurrent.atomic.AtomicReference(actualValue); + value = ((actualValue == null) ? this.intArrayField : actualValue); this.intArrayField.set(value); } } } - return value.get(); + return (int[]) ((value == this.intArrayField) ? null : value); } } -- cgit From 91bfd390e05ed3d04dff6438ffd1bc9e01eb1fff Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 21:49:23 +0200 Subject: updated docs for new desugaring for getter(lazy=true) --- doc/changelog.markdown | 1 + usage_examples/GetterLazyExample_post.jpage | 19 ++++++++++--------- website/features/GetterLazy.html | 10 ++-------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index e922f8c9..c9dafc61 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) diff --git a/usage_examples/GetterLazyExample_post.jpage b/usage_examples/GetterLazyExample_post.jpage index 9f4b1ba3..afed1748 100644 --- a/usage_examples/GetterLazyExample_post.jpage +++ b/usage_examples/GetterLazyExample_post.jpage @@ -1,18 +1,19 @@ public class GetterLazyExample { - private double[] $lombok$lazy1v; - private volatile boolean $lombok$lazy1i; - private final Object $lombok$lazyLock = new Object[0]; + private final java.util.concurrent.AtomicReference cached = new java.util.concurrent.AtomicReference(); public double[] getCached() { - if (!this.$lombok$lazy1i) { - synchronized (this.$lombok$lazyLock) { - if (!this.$lombok$lazy1i) { - this.$lombok$lazy1v = expensive(); - this.$lombok$lazy1i = true; + java.lang.Object value = this.cached.get(); + if (value == null) { + synchronized(value) { + value = this.cached.get(); + if (value == null) { + final double[] actualValue = expensive(); + value = actualValue == null ? this.cached : actualValue; + this.cached.set(value); } } } - return this.$lombok$lazy1v; + return (double[])(value == this.cached ? null : value); } private double[] expensive() { diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index f70c8e78..c6d21f01 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -15,12 +15,7 @@

Overview

- NEW IN Lombok 0.10: You can let lombok generate a getter which will calculate a value once, the first time this getter is called, and cache it from then on. This can be useful - if calculating the value takes a lot of CPU, or the value takes a lot of memory. To use this feature, create a private final variable, - initialize it with the expression that's expensive to run, and annotate your field with @Getter(lazy=true). The field will be hidden from the - rest of your code, and the expression will be evaluated no more than once, when the getter is first called. There are no magic marker values (i.e. even - if the result of your expensive calculation is null, the result is cached) and your expensive calculation need not be thread-safe, as lombok - takes care of locking. + NEW IN Lombok 0.10: You can let lombok generate a getter which will calculate a value once, the first time this getter is called, and cache it from then on. This can be useful if calculating the value takes a lot of CPU, or the value takes a lot of memory. To use this feature, create a private final variable, initialize it with the expression that's expensive to run, and annotate your field with @Getter(lazy=true). The field will be hidden from the rest of your code, and the expression will be evaluated no more than once, when the getter is first called. There are no magic marker values (i.e. even if the result of your expensive calculation is null, the result is cached) and your expensive calculation need not be thread-safe, as lombok takes care of locking.

@@ -38,8 +33,7 @@

Small print

- Lombok actually creates a few fields all prefixed with $lombok$ to cache the value. You should not rely on the exact type, name, and structure - of these fields as future implementations may change them. To access the lazily initialized value, always use the generated getter. + You should never refer to the field directly, always use the getter generated by lombok, because the type of the field will be mangled into an AtomicReference. Do not try to directly access this AtomicReference; if it points to itself, the value has been calculated, and it is null. If the reference points to null, then the value has not been calculated. This behaviour may change in future versions. Therefore, always use the generated getter to access your field!

Other Lombok annotations such as @ToString always call the getter even if you use doNotUseGetters=true.

-- cgit From 4923506d737718cec49e35aa9a273b3a999eefc6 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 22:05:41 +0200 Subject: more work on the tricky Context hack to make 'ant compile' not emit warnings. --- .../lombok/javac/java6/CommentCollectingScannerFactory.java | 10 ++++++++-- .../lombok/javac/java7/CommentCollectingScannerFactory.java | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index c345526e..b7d8ed13 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -49,15 +49,21 @@ public class CommentCollectingScannerFactory extends Scanner.Factory { // * Leave the return types as 'j.l.Object'. // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. - context.put(scannerFactoryKey, new Context.Factory() { + @SuppressWarnings("all") + class MyFactory implements Context.Factory { + // This overrides the javac6- version of make. public Object make() { return new CommentCollectingScannerFactory(context); } + // This overrides the javac7+ version of make. public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + + @SuppressWarnings("unchecked") Context.Factory factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } diff --git a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java index 2032e494..626d3d63 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java @@ -50,7 +50,8 @@ public class CommentCollectingScannerFactory extends ScannerFactory { // * Leave the return types as 'j.l.Object'. // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. - context.put(scannerFactoryKey, new Context.Factory() { + @SuppressWarnings("all") + class MyFactory implements Context.Factory { // This overrides the javac6- version of make. public Object make() { return new CommentCollectingScannerFactory(context); @@ -60,7 +61,9 @@ public class CommentCollectingScannerFactory extends ScannerFactory { public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + @SuppressWarnings("unchecked") Context.Factory factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } -- cgit From a6c1be550fd1911084faaf7f54ae7bbbd5673642 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 04:58:00 +0200 Subject: A lot of refactoring on how javadoc is handled, to prepare for copying javadoc from field to setter/getter in javac. --- .../lombok/delombok/PrettyCommentsPrinter.java | 32 +- .../sun/tools/javac/parser/DocCommentScanner.java | 25 ++ src/stubs/com/sun/tools/javac/parser/Scanner.java | 7 + .../com/sun/tools/javadoc/DocCommentScanner.java | 25 ++ src/utils/lombok/javac/CommentCatcher.java | 4 +- src/utils/lombok/javac/CommentInfo.java | 2 +- .../java6/CommentCollectingParserFactory.java | 2 +- .../javac/java6/CommentCollectingScanner.java | 5 +- .../java6/CommentCollectingScannerFactory.java | 2 +- .../lombok/javac/java6/DocCommentScanner.java | 461 +++++++++++++++++++++ .../java7/CommentCollectingParserFactory.java | 4 +- .../javac/java7/CommentCollectingScanner.java | 7 +- 12 files changed, 557 insertions(+), 19 deletions(-) create mode 100644 src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java create mode 100644 src/stubs/com/sun/tools/javadoc/DocCommentScanner.java create mode 100644 src/utils/lombok/javac/java6/DocCommentScanner.java diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 9c6a2bd7..342e3323 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -224,12 +224,21 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { return tree.getEndPosition(cu.endPositions); } - private void consumeComments(int till) throws IOException { + private void consumeComments(int until) throws IOException { + consumeComments(until, null); + } + private void consumeComments(int until, JCTree tree) throws IOException { boolean prevNewLine = onNewLine; boolean found = false; CommentInfo head = comments.head; - while (comments.nonEmpty() && head.pos < till) { - printComment(head); + while (comments.nonEmpty() && head.pos < until) { + if (tree != null && docComments != null && docComments.containsKey(tree) && head.isJavadoc() && noFurtherJavadocForthcoming(until)) { + // This is (presumably) the exact same javadoc that has already been associated with the node that we're just about to + // print. These javadoc can be modified by lombok handlers, and as such we should NOT print them from the consumed comments db, + // and instead print the actual javadoc associated with the upcoming node (which the visit method for that node will take care of). + } else { + printComment(head); + } comments = comments.tail; head = comments.head; } @@ -237,6 +246,17 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { println(); } } + + private boolean noFurtherJavadocForthcoming(int until) { + List c = comments; + if (c.nonEmpty()) c = c.tail; + while (c.nonEmpty()) { + if (c.head.pos >= until) return true; + if (c.head.isJavadoc()) return false; + c = c.tail; + } + return true; + } private void consumeTrailingComments(int from) throws IOException { boolean prevNewLine = onNewLine; @@ -408,7 +428,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { this.prec = prec; if (tree == null) print("/*missing*/"); else { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); tree.accept(this); int endPos = endPos(tree); consumeTrailingComments(endPos); @@ -620,7 +640,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { docComments = tree.docComments; printDocComment(tree); if (tree.pid != null) { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); print("package "); printExpr(tree.pid); print(";"); @@ -694,7 +714,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitClassDef(JCClassDecl tree) { try { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); println(); align(); printDocComment(tree); printAnnotations(tree.mods.annotations); diff --git a/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java new file mode 100644 index 00000000..d461d54b --- /dev/null +++ b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.parser; + +import java.nio.CharBuffer; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + protected DocCommentScanner(ScannerFactory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(ScannerFactory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + +} diff --git a/src/stubs/com/sun/tools/javac/parser/Scanner.java b/src/stubs/com/sun/tools/javac/parser/Scanner.java index af94bacc..266208e5 100644 --- a/src/stubs/com/sun/tools/javac/parser/Scanner.java +++ b/src/stubs/com/sun/tools/javac/parser/Scanner.java @@ -59,4 +59,11 @@ public class Scanner implements Lexer { public char[] getRawCharacters(int beginIndex, int endIndex) { return null; } + + public void nextToken() { + } + + public char[] getRawCharacters() { + return new char[0]; + } } diff --git a/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java new file mode 100644 index 00000000..e2ea9614 --- /dev/null +++ b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javadoc; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + public static class Factory extends Scanner.Factory { + protected Factory(Context context) { + super(context); + } + } +} diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index 2825cd30..565a166d 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -78,10 +78,10 @@ public class CommentCatcher { try { if (Javac.getJavaCompilerVersion() <= 6) { Class parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); } else { Class parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); } } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; diff --git a/src/utils/lombok/javac/CommentInfo.java b/src/utils/lombok/javac/CommentInfo.java index 7375d51a..afdd8469 100644 --- a/src/utils/lombok/javac/CommentInfo.java +++ b/src/utils/lombok/javac/CommentInfo.java @@ -66,7 +66,7 @@ public final class CommentInfo { } public boolean isJavadoc() { - return content.startsWith("/**"); + return content.startsWith("/**") && content.length() > 4; } @Override diff --git a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java index b2a248c8..7e34b723 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java @@ -25,7 +25,7 @@ public class CommentCollectingParserFactory extends Parser.Factory { } @Override public Parser newParser(Lexer S, boolean keepDocComments, boolean genEndPos) { - Object x = new CommentCollectingParser(this, S, keepDocComments, commentsMap); + Object x = new CommentCollectingParser(this, S, true, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java6/CommentCollectingScanner.java b/src/utils/lombok/javac/java6/CommentCollectingScanner.java index 66e1514d..b584ec16 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScanner.java @@ -27,12 +27,10 @@ import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; -import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer comments = ListBuffer.lb(); private int endComment = 0; @@ -56,6 +54,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index b7d8ed13..f3d6bd72 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -26,7 +26,7 @@ import java.nio.CharBuffer; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.Context; -public class CommentCollectingScannerFactory extends Scanner.Factory { +public class CommentCollectingScannerFactory extends DocCommentScanner.Factory { @SuppressWarnings("all") public static void preRegister(final Context context) { diff --git a/src/utils/lombok/javac/java6/DocCommentScanner.java b/src/utils/lombok/javac/java6/DocCommentScanner.java new file mode 100644 index 00000000..ff3eadd4 --- /dev/null +++ b/src/utils/lombok/javac/java6/DocCommentScanner.java @@ -0,0 +1,461 @@ +package lombok.javac.java6; + +/* + * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import static com.sun.tools.javac.util.LayoutCharacters.*; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Position; + +/** An extension to the base lexical analyzer that captures + * and processes the contents of doc comments. It does so by + * translating Unicode escape sequences and by stripping the + * leading whitespace and starts from each line of the comment. + * + *

This is NOT part of any API supported by Sun Microsystems. If + * you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class DocCommentScanner extends Scanner { + + /** A factory for creating scanners. */ + public static class Factory extends Scanner.Factory { + + @SuppressWarnings({"unchecked", "all"}) + public static void preRegister(final Context context) { + context.put(scannerFactoryKey, new Context.Factory() { + public Object make() { + return new Factory(context); + } + + public Object make(Context c) { + return new Factory(c); + } + }); + } + + /** Create a new scanner factory. */ + protected Factory(Context context) { + super(context); + } + + @Override + public Scanner newScanner(CharSequence input) { + if (input instanceof CharBuffer) { + return new DocCommentScanner(this, (CharBuffer)input); + } else { + char[] array = input.toString().toCharArray(); + return newScanner(array, array.length); + } + } + + @Override + public Scanner newScanner(char[] input, int inputLength) { + return new DocCommentScanner(this, input, inputLength); + } + } + + + /** Create a scanner from the input buffer. buffer must implement + * array() and compact(), and remaining() must be less than limit(). + */ + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + /** Create a scanner from the input array. The array must have at + * least a single character of extra space. + */ + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + /** Starting position of the comment in original source + */ + private int pos; + + /** The comment input buffer, index of next chacter to be read, + * index of one past last character in buffer. + */ + private char[] buf; + private int bp; + private int buflen; + + /** The current character. + */ + private char ch; + + /** The column number position of the current character. + */ + private int col; + + /** The buffer index of the last converted Unicode character + */ + private int unicodeConversionBp = 0; + + /** + * Buffer for doc comment. + */ + private char[] docCommentBuffer = new char[1024]; + + /** + * Number of characters in doc comment buffer. + */ + private int docCommentCount; + + /** + * Translated and stripped contents of doc comment + */ + private String docComment = null; + + + /** Unconditionally expand the comment buffer. + */ + private void expandCommentBuffer() { + char[] newBuffer = new char[docCommentBuffer.length * 2]; + System.arraycopy(docCommentBuffer, 0, newBuffer, + 0, docCommentBuffer.length); + docCommentBuffer = newBuffer; + } + + /** Convert an ASCII digit from its base (8, 10, or 16) + * to its value. + */ + private int digit(int base) { + char c = ch; + int result = Character.digit(c, base); + if (result >= 0 && c > 0x7f) { + ch = "0123456789abcdef".charAt(result); + } + return result; + } + + /** Convert Unicode escape; bp points to initial '\' character + * (Spec 3.3). + */ + private void convertUnicode() { + if (ch == '\\' && unicodeConversionBp != bp) { + bp++; ch = buf[bp]; col++; + if (ch == 'u') { + do { + bp++; ch = buf[bp]; col++; + } while (ch == 'u'); + int limit = bp + 3; + if (limit < buflen) { + int d = digit(16); + int code = d; + while (bp < limit && d >= 0) { + bp++; ch = buf[bp]; col++; + d = digit(16); + code = (code << 4) + d; + } + if (d >= 0) { + ch = (char)code; + unicodeConversionBp = bp; + return; + } + } + // "illegal.Unicode.esc", reported by base scanner + } else { + bp--; + ch = '\\'; + col--; + } + } + } + + + /** Read next character. + */ + private void scanChar() { + bp++; + ch = buf[bp]; + switch (ch) { + case '\r': // return + col = 0; + break; + case '\n': // newline + if (bp == 0 || buf[bp-1] != '\r') { + col = 0; + } + break; + case '\t': // tab + col = (col / TabInc * TabInc) + TabInc; + break; + case '\\': // possible Unicode + col++; + convertUnicode(); + break; + default: + col++; + break; + } + } + + /** + * Read next character in doc comment, skipping over double '\' characters. + * If a double '\' is skipped, put in the buffer and update buffer count. + */ + private void scanDocCommentChar() { + scanChar(); + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + bp++; col++; + } else { + convertUnicode(); + } + } + } + + /* Reset doc comment before reading each new token + */ + public void nextToken() { + docComment = null; + super.nextToken(); + } + + /** + * Returns the documentation string of the current token. + */ + public String docComment() { + return docComment; + } + + /** + * Process a doc comment and make the string content available. + * Strips leading whitespace and stars. + */ + @SuppressWarnings("fallthrough") + protected void processComment(CommentStyle style) { + if (style != CommentStyle.JAVADOC) { + return; + } + + pos = pos(); + buf = getRawCharacters(pos, endPos()); + buflen = buf.length; + bp = 0; + col = 0; + + docCommentCount = 0; + + boolean firstLine = true; + + // Skip over first slash + scanDocCommentChar(); + // Skip over first star + scanDocCommentChar(); + + // consume any number of stars + while (bp < buflen && ch == '*') { + scanDocCommentChar(); + } + // is the comment in the form /**/, /***/, /****/, etc. ? + if (bp < buflen && ch == '/') { + docComment = ""; + return; + } + + // skip a newline on the first line of the comment. + if (bp < buflen) { + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } else if (ch == CR) { + scanDocCommentChar(); + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } + } + } + + outerLoop: + + // The outerLoop processes the doc comment, looping once + // for each line. For each line, it first strips off + // whitespace, then it consumes any stars, then it + // puts the rest of the line into our buffer. + while (bp < buflen) { + + // The wsLoop consumes whitespace from the beginning + // of each line. + wsLoop: + + while (bp < buflen) { + switch(ch) { + case ' ': + scanDocCommentChar(); + break; + case '\t': + col = ((col - 1) / TabInc * TabInc) + TabInc; + scanDocCommentChar(); + break; + case FF: + col = 0; + scanDocCommentChar(); + break; +// Treat newline at beginning of line (blank line, no star) +// as comment text. Old Javadoc compatibility requires this. +/*---------------------------------* + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch == LF) { + col = 0; + scanDocCommentChar(); + } + break; + case LF: // (Spec 3.4) + scanDocCommentChar(); + break; +*---------------------------------*/ + default: + // we've seen something that isn't whitespace; + // jump out. + break wsLoop; + } + } + + // Are there stars here? If so, consume them all + // and check for the end of comment. + if (ch == '*') { + // skip all of the stars + do { + scanDocCommentChar(); + } while (ch == '*'); + + // check for the closing slash. + if (ch == '/') { + // We're done with the doc comment + // scanChar() and breakout. + break outerLoop; + } + } else if (! firstLine) { + //The current line does not begin with a '*' so we will indent it. + for (int i = 1; i < col; i++) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ' '; + } + } + + // The textLoop processes the rest of the characters + // on the line, adding them to our buffer. + textLoop: + while (bp < buflen) { + switch (ch) { + case '*': + // Is this just a star? Or is this the + // end of a comment? + scanDocCommentChar(); + if (ch == '/') { + // This is the end of the comment, + // set ch and return our buffer. + break outerLoop; + } + // This is just an ordinary star. Add it to + // the buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = '*'; + break; + case ' ': + case '\t': + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break; + case FF: + scanDocCommentChar(); + break textLoop; // treat as end of line + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch != LF) { + // Canonicalize CR-only line terminator to LF + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = (char)LF; + break textLoop; + } + /* fall through to LF case */ + case LF: // (Spec 3.4) + // We've seen a newline. Add it to our + // buffer and break out of this loop, + // starting fresh on a new line. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break textLoop; + default: + // Add the character to our buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + } + } // end textLoop + firstLine = false; + } // end outerLoop + + if (docCommentCount > 0) { + int i = docCommentCount - 1; + trailLoop: + while (i > -1) { + switch (docCommentBuffer[i]) { + case '*': + i--; + break; + default: + break trailLoop; + } + } + docCommentCount = i + 1; + + // Store the text of the doc comment + docComment = new String(docCommentBuffer, 0 , docCommentCount); + } else { + docComment = ""; + } + } + + /** Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap */ + public Position.LineMap getLineMap() { + char[] buf = getRawCharacters(); + return Position.makeLineMap(buf, buf.length, true); + } +} diff --git a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java index e361a5bd..e9575c14 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java @@ -30,8 +30,8 @@ public class CommentCollectingParserFactory extends ParserFactory { public Parser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) { ScannerFactory scannerFactory = ScannerFactory.instance(context); - Lexer lexer = scannerFactory.newScanner(input, keepDocComments); - Object x = new CommentCollectingParser(this, lexer, keepDocComments, keepLineMap, commentsMap); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java7/CommentCollectingScanner.java b/src/utils/lombok/javac/java7/CommentCollectingScanner.java index e2d040f2..6ebd3ac1 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScanner.java @@ -26,12 +26,12 @@ import java.nio.CharBuffer; import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; + import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.parser.DocCommentScanner; -import com.sun.tools.javac.parser.Scanner; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer comments = ListBuffer.lb(); private int endComment = 0; @@ -55,6 +55,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { -- cgit From 0d5fea94da2bfb72ea886a7379ad35e124489692 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 05:50:08 +0200 Subject: Support for javadoc copying in Getter/Setter generation for javac, as well as updates to all relevant documentation --- doc/changelog.markdown | 1 + src/core/lombok/javac/handlers/HandleGetter.java | 1 + src/core/lombok/javac/handlers/HandleSetter.java | 4 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 91 ++++++++++++++++++++++ usage_examples/GetterSetterExample_post.jpage | 22 ++++++ usage_examples/GetterSetterExample_pre.jpage | 14 ++++ website/features/GetterSetter.html | 2 + 7 files changed, 134 insertions(+), 1 deletion(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index c9dafc61..75825d77 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* FEATURE: javadoc on fields will now be copied to generated getters / setters. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). * CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 413404c0..51642f86 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -253,6 +253,7 @@ public class HandleGetter extends JavacAnnotationHandler { if (toClearOfMarkers != null) recursiveSetGeneratedBy(toClearOfMarkers, null); decl.mods.annotations = decl.mods.annotations.appendList(delegates); + copyJavadoc(field, decl, CopyJavadoc.GETTER); return decl; } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 29728eae..282e6c2f 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -252,8 +252,10 @@ public class HandleSetter extends JavacAnnotationHandler { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.SETTER); + return decl; } private static class JCNoType extends Type implements NoType { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 1784be90..87493e39 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; +import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.AccessLevel; @@ -1163,4 +1164,94 @@ public class JavacHandlerUtil { // This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline. return (JCExpression) in; } + + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*(GETTER|SETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + + private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { + Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(javadoc); + return m.replaceAll(""); + } + + private static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) { + Matcher m = SECTION_FINDER.matcher(javadoc); + int getterSectionHeaderStart = -1; + int getterSectionStart = -1; + int getterSectionEnd = -1; + while (m.find()) { + if (m.group(1).equalsIgnoreCase(sectionName)) { + getterSectionStart = m.end() + 1; + getterSectionHeaderStart = m.start(); + } else if (getterSectionStart != -1) { + getterSectionEnd = m.start(); + } + } + + if (getterSectionStart != -1) { + if (getterSectionEnd != -1) { + return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)}; + } else { + return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)}; + } + } + + return null; + } + + public static enum CopyJavadoc { + VERBATIM, GETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @return from the original and @param from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*"); + return new String[] {copy, javadoc}; + } + }, + SETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "SETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + }; + + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ + public String[] split(String javadoc) { + return new String[] {javadoc, javadoc}; + } + } + + /** + * Copies javadoc on one node to the other. + * + * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is + * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines. any {@code @return} lines + * are stripped from 'from'. + * + * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped. + */ + public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) { + if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; + try { + JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); + if (cu.docComments != null) { + String javadoc = cu.docComments.get(from.get()); + + if (javadoc != null) { + String[] filtered = copyMode.split(javadoc); + cu.docComments.put(to, filtered[0]); + cu.docComments.put(from.get(), filtered[1]); + } + } + } catch (Exception ignore) {} + } } diff --git a/usage_examples/GetterSetterExample_post.jpage b/usage_examples/GetterSetterExample_post.jpage index b99ba7e1..241a3a4e 100644 --- a/usage_examples/GetterSetterExample_post.jpage +++ b/usage_examples/GetterSetterExample_post.jpage @@ -1,19 +1,41 @@ public class GetterSetterExample { + /** + * Age of the person. Water is wet. + */ private int age = 10; + + /** + * Name of the person. + */ private String name; @Override public String toString() { return String.format("%s (age: %d)", name, age); } + /** + * Age of the person. Water is wet. + * + * @return The current value of this person's age. Circles are round. + */ public int getAge() { return age; } + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + */ public void setAge(int age) { this.age = age; } + /** + * Changes the name of this person. + * + * @param name The new value. + */ protected void setName(String name) { this.name = name; } diff --git a/usage_examples/GetterSetterExample_pre.jpage b/usage_examples/GetterSetterExample_pre.jpage index 9ef0532c..4183aa5d 100644 --- a/usage_examples/GetterSetterExample_pre.jpage +++ b/usage_examples/GetterSetterExample_pre.jpage @@ -3,7 +3,21 @@ import lombok.Getter; import lombok.Setter; public class GetterSetterExample { + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + * @return The current value of this person's age. Circles are round. + */ @Getter @Setter private int age = 10; + + /** + * Name of the person. + * -- SETTER -- + * Changes the name of this person. + * + * @param name The new value. + */ @Setter(AccessLevel.PROTECTED) private String name; @Override public String toString() { diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index c78b03bd..7e2ff226 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -30,6 +30,8 @@ behaviour of a @Getter, @Setter or @Data annotation on a class.

To put annotations on the generated method, you can use onMethod=@_({@AnnotationsHere}); to put annotations on the only parameter of a generated setter method, you can use onParam=@_({@AnnotationsHere}). Be careful though! This is an experimental feature. For more details see the documentation on the onX feature. +

+ NEW in lombok v1.12.0: javadoc on the field will now be copied to generated getters and setters. Normally, all text is copied, and @return is moved to the getter, whilst @param lines are moved to the setter. Moved means: Deleted from the field's javadoc. It is also possible to define unique text for each getter/setter. To do that, you create a 'section' named GETTER and/or SETTER. A section is a line in your javadoc containing 2 or more dashes, then the text 'GETTER' or 'SETTER', followed by 2 or more dashes, and nothing else on the line. If you use sections, @return and @param stripping for that section is no longer done (move the @return or @param line into the section).

-- cgit From 867929c79f344e2fa63aa280cf452d39eb7aabf7 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 06:06:49 +0200 Subject: added tests for new javadoc behaviour, javac implementation of copying javadoc for getters/setters, and fixed pretty printer to no longer inject an extra newline at the top of javadoc. --- .../lombok/delombok/PrettyCommentsPrinter.java | 7 ++ .../after-delombok/CommentsInterspersed.java | 8 +- .../after-delombok/DelegateWithDeprecated.java | 4 +- .../resource/after-delombok/GetterDeprecated.java | 3 + .../after-delombok/GetterSetterJavadoc.java | 100 +++++++++++++++++++++ .../resource/after-delombok/JavadocGenerally.java | 24 +++++ .../resource/after-delombok/SetterDeprecated.java | 3 + .../resource/before/CommentsInterspersed.java | 8 +- .../resource/before/DelegateWithDeprecated.java | 4 +- .../resource/before/GetterSetterJavadoc.java | 37 ++++++++ .../resource/before/JavadocGenerally.java | 27 ++++++ 11 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 test/transform/resource/after-delombok/GetterSetterJavadoc.java create mode 100644 test/transform/resource/after-delombok/JavadocGenerally.java create mode 100644 test/transform/resource/before/GetterSetterJavadoc.java create mode 100644 test/transform/resource/before/JavadocGenerally.java diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 342e3323..1bb2ce23 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -530,7 +530,14 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { print("/**"); println(); int pos = 0; int endpos = lineEndPos(dc, pos); + boolean atStart = true; while (pos < dc.length()) { + String line = dc.substring(pos, endpos); + if (line.trim().isEmpty() && atStart) { + atStart = false; + continue; + } + atStart = false; align(); print(" *"); if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); diff --git a/test/transform/resource/after-delombok/CommentsInterspersed.java b/test/transform/resource/after-delombok/CommentsInterspersed.java index 5aaafe42..833f2ce0 100644 --- a/test/transform/resource/after-delombok/CommentsInterspersed.java +++ b/test/transform/resource/after-delombok/CommentsInterspersed.java @@ -1,9 +1,13 @@ /* cmt *//* cmt2 */ /* cmt3 */ /*bla */ public class CommentsInterspersed { - /** javadoc for field */ + /** + * javadoc for field + */ private int x; /* bla2 */ private String test = "foo"; //$NON-NLS-1$ - /** Javadoc on method */ + /** + * Javadoc on method + */ public native void gwtTest(); /*-{ javascript; }-*/ diff --git a/test/transform/resource/after-delombok/DelegateWithDeprecated.java b/test/transform/resource/after-delombok/DelegateWithDeprecated.java index 04e12160..f7bd1e6d 100644 --- a/test/transform/resource/after-delombok/DelegateWithDeprecated.java +++ b/test/transform/resource/after-delombok/DelegateWithDeprecated.java @@ -3,7 +3,9 @@ class DelegateWithDeprecated { private interface Bar { @Deprecated void deprecatedAnnotation(); - /** @deprecated */ + /** + * @deprecated + */ void deprecatedComment(); void notDeprecated(); } diff --git a/test/transform/resource/after-delombok/GetterDeprecated.java b/test/transform/resource/after-delombok/GetterDeprecated.java index 3387540f..09ea9929 100644 --- a/test/transform/resource/after-delombok/GetterDeprecated.java +++ b/test/transform/resource/after-delombok/GetterDeprecated.java @@ -10,6 +10,9 @@ class GetterDeprecated { public int getAnnotation() { return this.annotation; } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public int getJavadoc() { diff --git a/test/transform/resource/after-delombok/GetterSetterJavadoc.java b/test/transform/resource/after-delombok/GetterSetterJavadoc.java new file mode 100644 index 00000000..7bf92d1f --- /dev/null +++ b/test/transform/resource/after-delombok/GetterSetterJavadoc.java @@ -0,0 +1,100 @@ +class GetterSetterJavadoc1 { + /** + * Some text + */ + private int fieldName; + @java.lang.SuppressWarnings("all") + public GetterSetterJavadoc1() { + } + /** + * Getter section + * + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Some text + * + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof GetterSetterJavadoc1)) return false; + final GetterSetterJavadoc1 other = (GetterSetterJavadoc1)o; + if (!other.canEqual((java.lang.Object)this)) return false; + if (this.getFieldName() != other.getFieldName()) return false; + return true; + } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof GetterSetterJavadoc1; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + this.getFieldName(); + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "GetterSetterJavadoc1(fieldName=" + this.getFieldName() + ")"; + } +} +class GetterSetterJavadoc2 { + /** + * Some text + */ + private int fieldName; + /** + * Some text + * + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Some text + * + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} +class GetterSetterJavadoc3 { + /** + * Some text + */ + private int fieldName; + /** + * Getter section + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Setter section + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-delombok/JavadocGenerally.java b/test/transform/resource/after-delombok/JavadocGenerally.java new file mode 100644 index 00000000..729cdce3 --- /dev/null +++ b/test/transform/resource/after-delombok/JavadocGenerally.java @@ -0,0 +1,24 @@ +/** + * Doc on package + */ +package testPackage; +/** Weird doc */ +/** + * Doc on class + */ +class JavadocGenerally { + /** + * Doc on field + */ + private int someField; + /** + * Doc on method + */ + public void test() { + } + /** + * Doc on inner + */ + public interface TestingInner { + } +} diff --git a/test/transform/resource/after-delombok/SetterDeprecated.java b/test/transform/resource/after-delombok/SetterDeprecated.java index 5a6cf9f3..72a609ad 100644 --- a/test/transform/resource/after-delombok/SetterDeprecated.java +++ b/test/transform/resource/after-delombok/SetterDeprecated.java @@ -10,6 +10,9 @@ class SetterDeprecated { public void setAnnotation(final int annotation) { this.annotation = annotation; } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public void setJavadoc(final int javadoc) { diff --git a/test/transform/resource/before/CommentsInterspersed.java b/test/transform/resource/before/CommentsInterspersed.java index e7898aaa..23a1060b 100644 --- a/test/transform/resource/before/CommentsInterspersed.java +++ b/test/transform/resource/before/CommentsInterspersed.java @@ -1,12 +1,16 @@ import /* cmt */ lombok./* cmt2 */Getter /* cmt3 */ ; public /*bla */ class CommentsInterspersed { - /** javadoc for field */ + /** + * javadoc for field + */ private int x; private /* bla2 */ @Getter String test = "foo"; //$NON-NLS-1$ - /** Javadoc on method */ + /** + * Javadoc on method + */ public native void gwtTest(); /*-{ javascript; }-*/ diff --git a/test/transform/resource/before/DelegateWithDeprecated.java b/test/transform/resource/before/DelegateWithDeprecated.java index b748c6ec..064e951d 100644 --- a/test/transform/resource/before/DelegateWithDeprecated.java +++ b/test/transform/resource/before/DelegateWithDeprecated.java @@ -6,7 +6,9 @@ class DelegateWithDeprecated { private interface Bar { @Deprecated void deprecatedAnnotation(); - /** @deprecated */ + /** + * @deprecated + */ void deprecatedComment(); void notDeprecated(); } diff --git a/test/transform/resource/before/GetterSetterJavadoc.java b/test/transform/resource/before/GetterSetterJavadoc.java new file mode 100644 index 00000000..e3ae0aac --- /dev/null +++ b/test/transform/resource/before/GetterSetterJavadoc.java @@ -0,0 +1,37 @@ +@lombok.Data +class GetterSetterJavadoc1 { + /** + * Some text + * + * @param fieldName Hello, World + * --- GETTER --- + * Getter section + * + * @return Sky is blue + */ + private int fieldName; +} + +class GetterSetterJavadoc2 { + /** + * Some text + * + * @param fieldName Hello, World + * @return Sky is blue + */ + @lombok.Getter @lombok.Setter private int fieldName; +} + +class GetterSetterJavadoc3 { + /** + * Some text + * + * **SETTER** + * Setter section + * @param fieldName Hello, World + * **GETTER** + * Getter section + * @return Sky is blue + */ + @lombok.Getter @lombok.Setter private int fieldName; +} diff --git a/test/transform/resource/before/JavadocGenerally.java b/test/transform/resource/before/JavadocGenerally.java new file mode 100644 index 00000000..ee015acf --- /dev/null +++ b/test/transform/resource/before/JavadocGenerally.java @@ -0,0 +1,27 @@ +/** + * Doc on package + */ +package testPackage; + +/** Weird doc */ +/** + * Doc on class + */ +class JavadocGenerally { + /** + * Doc on field + */ + private int someField; + + /** + * Doc on method + */ + public void test() { + } + + /** + * Doc on inner + */ + public interface TestingInner { + } +} -- cgit From bf8e2afed07b4062ac3d3fee8b2f8981d1213f7e Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 20:56:50 +0200 Subject: gave up on adding support for moving javadoc to getter/setter in eclipse. Eclipse breaks the javadoc out of the raw source and we can't modify that without breaking a billion things. To solve this issue we'd have to write some very complicated patches to intercept this process and somehow propagate the node that the javadoc is attached to AND translate from the ast model to dom or whatever is being used there. Not gonna happen for this low priority feature. --- .../lombok/javac/handlers/JavacHandlerUtil.java | 4 +- test/core/src/lombok/RunTestsViaEcj.java | 2 +- .../resource/after-ecj/GetterSetterJavadoc.java | 60 ++++++++++++++++++++++ .../resource/after-ecj/JavadocGenerally.java | 11 ++++ 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 test/transform/resource/after-ecj/GetterSetterJavadoc.java create mode 100644 test/transform/resource/after-ecj/JavadocGenerally.java diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 87493e39..6aed5508 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1165,7 +1165,7 @@ public class JavacHandlerUtil { return (JCExpression) in; } - private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*(GETTER|SETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); @@ -1201,7 +1201,7 @@ public class JavacHandlerUtil { public static enum CopyJavadoc { VERBATIM, GETTER { @Override public String[] split(String javadoc) { - // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new one and we strip that from the original. + // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc and we strip that from the original. String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); if (out != null) return out; // failing that, create a copy, but strip @return from the original and @param from the copy. diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index 12f2e252..f7294f1f 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -54,6 +54,7 @@ public class RunTestsViaEcj extends AbstractRunTests { options.complianceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); options.sourceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); options.targetJDK = Eclipse.getLatestEcjCompilerVersionConstant(); + options.docCommentSupport = false; options.parseLiteralExpressionsAsConstants = true; options.inlineJsrBytecode = true; options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false; @@ -64,7 +65,6 @@ public class RunTestsViaEcj extends AbstractRunTests { options.reportUnusedParameterWhenOverridingConcrete = false; options.reportDeadCodeInTrivialIfStatement = false; options.generateClassFiles = false; - options.docCommentSupport = false; Map warnings = new HashMap(); warnings.put(CompilerOptions.OPTION_ReportUnusedLocal, "ignore"); warnings.put(CompilerOptions.OPTION_ReportUnusedLabel, "ignore"); diff --git a/test/transform/resource/after-ecj/GetterSetterJavadoc.java b/test/transform/resource/after-ecj/GetterSetterJavadoc.java new file mode 100644 index 00000000..73f26180 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterSetterJavadoc.java @@ -0,0 +1,60 @@ +@lombok.Data class GetterSetterJavadoc1 { + private int fieldName; + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof GetterSetterJavadoc1))) + return false; + final @java.lang.SuppressWarnings("all") GetterSetterJavadoc1 other = (GetterSetterJavadoc1) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((this.getFieldName() != other.getFieldName())) + return false; + return true; + } + public @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof GetterSetterJavadoc1); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + this.getFieldName()); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("GetterSetterJavadoc1(fieldName=" + this.getFieldName()) + ")"); + } + public @java.lang.SuppressWarnings("all") GetterSetterJavadoc1() { + super(); + } +} +class GetterSetterJavadoc2 { + private @lombok.Getter @lombok.Setter int fieldName; + GetterSetterJavadoc2() { + super(); + } + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} +class GetterSetterJavadoc3 { + private @lombok.Getter @lombok.Setter int fieldName; + GetterSetterJavadoc3() { + super(); + } + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/JavadocGenerally.java b/test/transform/resource/after-ecj/JavadocGenerally.java new file mode 100644 index 00000000..be9ed756 --- /dev/null +++ b/test/transform/resource/after-ecj/JavadocGenerally.java @@ -0,0 +1,11 @@ +package testPackage; +class JavadocGenerally { + public interface TestingInner { + } + private int someField; + JavadocGenerally() { + super(); + } + public void test() { + } +} \ No newline at end of file -- cgit From e7ff097fec867714b8a064b559dfc9e5162a489c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 21:17:01 +0200 Subject: Fixed value's snippet integration (it hadn't been updated yet now that Value has moved from experimental into core). --- buildScripts/website.ant.xml | 6 +- doc/changelog.markdown | 2 +- src/core/lombok/javac/handlers/HandleWither.java | 5 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 27 +++-- .../resource/after-delombok/WitherDeprecated.java | 3 + usage_examples/ValueExample_post.jpage | 120 +++++++++++++++++++++ usage_examples/ValueExample_pre.jpage | 19 ++++ .../experimental/ValueExample_post.jpage | 120 --------------------- usage_examples/experimental/ValueExample_pre.jpage | 19 ---- website/features/experimental/Wither.html | 2 + 10 files changed, 170 insertions(+), 153 deletions(-) create mode 100644 usage_examples/ValueExample_post.jpage create mode 100644 usage_examples/ValueExample_pre.jpage delete mode 100644 usage_examples/experimental/ValueExample_post.jpage delete mode 100644 usage_examples/experimental/ValueExample_pre.jpage diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 521ca59b..baf869a4 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -142,6 +142,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + @@ -163,9 +166,6 @@ such as converting the changelog into HTML, and creating javadoc. - - - diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 75825d77..d15e24be 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,7 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) -* FEATURE: javadoc on fields will now be copied to generated getters / setters. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). +* FEATURE: javadoc on fields will now be copied to generated getters / setters / withers. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). [@Wither documentation](http://projectlombok.org/features/experimental/Wither.html). * CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index ba5aa72d..b3f218b8 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -33,6 +33,7 @@ import lombok.core.TransformationsUtil; import lombok.experimental.Wither; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -264,7 +265,9 @@ public class HandleWither extends JavacAnnotationHandler { if (isFieldDeprecated(field)) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.WITHER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 6aed5508..a24dad7d 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1165,7 +1165,7 @@ public class JavacHandlerUtil { return (JCExpression) in; } - private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); @@ -1213,17 +1213,26 @@ public class JavacHandlerUtil { }, SETTER { @Override public String[] split(String javadoc) { - // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. - String[] out = splitJavadocOnSectionIfPresent(javadoc, "SETTER"); - if (out != null) return out; - // failing that, create a copy, but strip @param from the original and @return from the copy. - String copy = javadoc; - javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); - copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); - return new String[] {copy, javadoc}; + return splitForSetters(javadoc, "SETTER"); + } + }, + WITHER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "WITHER"); } }; + private static String[] splitForSetters(String javadoc, String sectionName) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ public String[] split(String javadoc) { return new String[] {javadoc, javadoc}; diff --git a/test/transform/resource/after-delombok/WitherDeprecated.java b/test/transform/resource/after-delombok/WitherDeprecated.java index f076d90e..29192012 100644 --- a/test/transform/resource/after-delombok/WitherDeprecated.java +++ b/test/transform/resource/after-delombok/WitherDeprecated.java @@ -12,6 +12,9 @@ class WitherDeprecated { public WitherDeprecated withAnnotation(final int annotation) { return this.annotation == annotation ? this : new WitherDeprecated(annotation, this.javadoc); } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public WitherDeprecated withJavadoc(final int javadoc) { diff --git a/usage_examples/ValueExample_post.jpage b/usage_examples/ValueExample_post.jpage new file mode 100644 index 00000000..ac9b64d1 --- /dev/null +++ b/usage_examples/ValueExample_post.jpage @@ -0,0 +1,120 @@ +import java.util.Arrays; + +public final class ValueExample { + private final String name; + private int age; + private final double score; + protected final String[] tags; + + @java.beans.ConstructorProperties({"name", "age", "score", "tags"}) + public ValueExample(String name, int age, double score, String[] tags) { + this.name = name; + this.age = age; + this.score = score; + this.tags = tags; + } + + public String getName() { + return this.name; + } + + public int getAge() { + return this.age; + } + + public double getScore() { + return this.score; + } + + public String[] getTags() { + return this.tags; + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample)) return false; + final ValueExample other = (ValueExample)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + if (this.getAge() != other.getAge()) return false; + if (Double.compare(this.getScore(), other.getScore()) != 0) return false; + if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + result = result * PRIME + this.getAge(); + final long $score = Double.doubleToLongBits(this.getScore()); + result = result * PRIME + (int)($score >>> 32 ^ $score); + result = result * PRIME + Arrays.deepHashCode(this.getTags()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")"; + } + + ValueExample withAge(int age) { + return this.age == age ? this : new ValueExample(name, age, score, tags); + } + + public static final class Exercise { + private final String name; + private final T value; + + private Exercise(String name, T value) { + this.name = name; + this.value = value; + } + + public static Exercise of(String name, T value) { + return new Exercise(name, value); + } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample.Exercise)) return false; + final Exercise other = (Exercise)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + final Object this$value = this.getValue(); + final Object other$value = other.getValue(); + if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + final Object $value = this.getValue(); + result = result * PRIME + ($value == null ? 0 : $value.hashCode()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; + } + } +} \ No newline at end of file diff --git a/usage_examples/ValueExample_pre.jpage b/usage_examples/ValueExample_pre.jpage new file mode 100644 index 00000000..d9550c25 --- /dev/null +++ b/usage_examples/ValueExample_pre.jpage @@ -0,0 +1,19 @@ +import lombok.AccessLevel; +import lombok.experimental.NonFinal; +import lombok.experimental.Value; +import lombok.experimental.Wither; +import lombok.ToString; + +@Value public class ValueExample { + String name; + @Wither(AccessLevel.PACKAGE) @NonFinal int age; + double score; + protected String[] tags; + + @ToString(includeFieldNames=true) + @Value(staticConstructor="of") + public static class Exercise { + String name; + T value; + } +} diff --git a/usage_examples/experimental/ValueExample_post.jpage b/usage_examples/experimental/ValueExample_post.jpage deleted file mode 100644 index ac9b64d1..00000000 --- a/usage_examples/experimental/ValueExample_post.jpage +++ /dev/null @@ -1,120 +0,0 @@ -import java.util.Arrays; - -public final class ValueExample { - private final String name; - private int age; - private final double score; - protected final String[] tags; - - @java.beans.ConstructorProperties({"name", "age", "score", "tags"}) - public ValueExample(String name, int age, double score, String[] tags) { - this.name = name; - this.age = age; - this.score = score; - this.tags = tags; - } - - public String getName() { - return this.name; - } - - public int getAge() { - return this.age; - } - - public double getScore() { - return this.score; - } - - public String[] getTags() { - return this.tags; - } - - @java.lang.Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof ValueExample)) return false; - final ValueExample other = (ValueExample)o; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; - if (this.getAge() != other.getAge()) return false; - if (Double.compare(this.getScore(), other.getScore()) != 0) return false; - if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 0 : $name.hashCode()); - result = result * PRIME + this.getAge(); - final long $score = Double.doubleToLongBits(this.getScore()); - result = result * PRIME + (int)($score >>> 32 ^ $score); - result = result * PRIME + Arrays.deepHashCode(this.getTags()); - return result; - } - - @java.lang.Override - public String toString() { - return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")"; - } - - ValueExample withAge(int age) { - return this.age == age ? this : new ValueExample(name, age, score, tags); - } - - public static final class Exercise { - private final String name; - private final T value; - - private Exercise(String name, T value) { - this.name = name; - this.value = value; - } - - public static Exercise of(String name, T value) { - return new Exercise(name, value); - } - - public String getName() { - return this.name; - } - - public T getValue() { - return this.value; - } - - @java.lang.Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof ValueExample.Exercise)) return false; - final Exercise other = (Exercise)o; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; - final Object this$value = this.getValue(); - final Object other$value = other.getValue(); - if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 0 : $name.hashCode()); - final Object $value = this.getValue(); - result = result * PRIME + ($value == null ? 0 : $value.hashCode()); - return result; - } - - @java.lang.Override - public String toString() { - return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; - } - } -} \ No newline at end of file diff --git a/usage_examples/experimental/ValueExample_pre.jpage b/usage_examples/experimental/ValueExample_pre.jpage deleted file mode 100644 index d9550c25..00000000 --- a/usage_examples/experimental/ValueExample_pre.jpage +++ /dev/null @@ -1,19 +0,0 @@ -import lombok.AccessLevel; -import lombok.experimental.NonFinal; -import lombok.experimental.Value; -import lombok.experimental.Wither; -import lombok.ToString; - -@Value public class ValueExample { - String name; - @Wither(AccessLevel.PACKAGE) @NonFinal int age; - double score; - protected String[] tags; - - @ToString(includeFieldNames=true) - @Value(staticConstructor="of") - public static class Exercise { - String name; - T value; - } -} diff --git a/website/features/experimental/Wither.html b/website/features/experimental/Wither.html index 9cbcd5ed..b334cd7c 100644 --- a/website/features/experimental/Wither.html +++ b/website/features/experimental/Wither.html @@ -46,6 +46,8 @@ a 'wither' is generated for each field (even non-final fields).

To put annotations on the generated method, you can use onMethod=@_({@AnnotationsHere}); to put annotations on the only parameter of a generated wither method, you can use onParam=@_({@AnnotationsHere}). Be careful though! This is an experimental feature. For more details see the documentation on the onX feature. +

+ NEW in lombok v1.12.0: javadoc on the field will now be copied to generated withers. Normally, all text is copied, and @param is moved to the wither, whilst @return lines are stripped from the wither's javadoc. Moved means: Deleted from the field's javadoc. It is also possible to define unique text for the wither's javadoc. To do that, you create a 'section' named WITHER. A section is a line in your javadoc containing 2 or more dashes, then the text 'WITHER', followed by 2 or more dashes, and nothing else on the line. If you use sections, @return and @param stripping / copying for that section is no longer done (move the @param line into the section).

-- cgit From 1037e8322560be0721d70c2c5bfeca9d42157d85 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 22:13:00 +0200 Subject: possible fix for a deadlock issue 531. --- .../ivy-repo/org.projectlombok-lombok.patcher-0.7.xml | 14 ++++++++++++++ buildScripts/ivy.xml | 2 +- doc/changelog.markdown | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml new file mode 100644 index 00000000..ca41f64c --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index fd6c503a..7afc8a64 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -12,7 +12,7 @@ - + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index d15e24be..95c31db2 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -10,6 +10,7 @@ Lombok Changelog * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) +* BUGFIX: Possibly fixed a race condition in patcher [Issue #531](https://code.google.com/p/projectlombok/issues/detail?id=531). ### v0.11.8 (April 23rd, 2013) * FEATURE: Major performance improvements in eclipse by profiling the project clean process. -- cgit From ec0cc4348cf71d872b796d0733fb64fc576ef5df Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jul 2013 00:45:09 +0200 Subject: Renamed ImmutableList to LombokImmutableList, to reduce our ImmutableList coming up in autocomplete dialogs when guava's was intended. --- src/core/lombok/core/AST.java | 2 +- src/core/lombok/core/LombokNode.java | 8 +- src/core/lombok/eclipse/EclipseAST.java | 4 +- src/utils/lombok/core/ImmutableList.java | 188 ------------------------- src/utils/lombok/core/JavaIdentifiers.java | 2 +- src/utils/lombok/core/LombokImmutableList.java | 188 +++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 196 deletions(-) delete mode 100644 src/utils/lombok/core/ImmutableList.java create mode 100644 src/utils/lombok/core/LombokImmutableList.java diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index e6721b80..6fed0252 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -174,7 +174,7 @@ public abstract class AST, L extends LombokNode, oldChild.parent = targetNode; } - targetNode.children = ImmutableList.copyOf(children); + targetNode.children = LombokImmutableList.copyOf(children); return targetNode; } diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 30bacc56..07c62151 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -42,7 +42,7 @@ public abstract class LombokNode, L extends LombokNode children; + protected LombokImmutableList children; protected L parent; /** structurally significant are those nodes that can be annotated in java 1.6 or are method-like toplevels, @@ -62,7 +62,7 @@ public abstract class LombokNode, L extends LombokNodeof(); + this.children = children != null ? LombokImmutableList.copyOf(children) : LombokImmutableList.of(); for (L child : this.children) { child.parent = (L) this; if (!child.isStructurallySignificant) @@ -176,7 +176,7 @@ public abstract class LombokNode, L extends LombokNode down() { + public LombokImmutableList down() { return children; } @@ -253,7 +253,7 @@ public abstract class LombokNode, L extends LombokNode { } void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { - ImmutableList children = node.down(); + LombokImmutableList children = node.down(); int len = children.size(); for (int i = 0; i < len; i++) { children.get(i).traverse(visitor); diff --git a/src/utils/lombok/core/ImmutableList.java b/src/utils/lombok/core/ImmutableList.java deleted file mode 100644 index 8b478dbc..00000000 --- a/src/utils/lombok/core/ImmutableList.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2013 The Project Lombok Authors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -public final class ImmutableList implements Iterable { - private Object[] content; - private static final ImmutableList EMPTY = new ImmutableList(new Object[0]); - - @SuppressWarnings("unchecked") - public static ImmutableList of() { - return (ImmutableList) EMPTY; - } - - public static ImmutableList of(T a) { - return new ImmutableList(new Object[] {a}); - } - - public static ImmutableList of(T a, T b) { - return new ImmutableList(new Object[] {a, b}); - } - - public static ImmutableList of(T a, T b, T c) { - return new ImmutableList(new Object[] {a, b, c}); - } - - public static ImmutableList of(T a, T b, T c, T d) { - return new ImmutableList(new Object[] {a, b, c, d}); - } - - public static ImmutableList of(T a, T b, T c, T d, T e) { - return new ImmutableList(new Object[] {a, b, c, d, e}); - } - - public static ImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { - Object[] rest = g == null ? new Object[] {null} : g; - Object[] val = new Object[rest.length + 6]; - System.arraycopy(rest, 0, val, 6, rest.length); - val[0] = a; - val[1] = b; - val[2] = c; - val[3] = d; - val[4] = e; - val[5] = f; - return new ImmutableList(val); - } - - public static ImmutableList copyOf(Collection list) { - return new ImmutableList(list.toArray()); - } - - public static ImmutableList copyOf(Iterable iterable) { - List list = new ArrayList(); - for (T o : iterable) list.add(o); - return copyOf(list); - } - - private ImmutableList(Object[] content) { - this.content = content; - } - - public ImmutableList replaceElementAt(int idx, T newValue) { - Object[] newContent = content.clone(); - newContent[idx] = newValue; - return new ImmutableList(newContent); - } - - public ImmutableList append(T newValue) { - int len = content.length; - Object[] newContent = new Object[len + 1]; - System.arraycopy(content, 0, newContent, 0, len); - newContent[len] = newValue; - return new ImmutableList(newContent); - } - - public ImmutableList prepend(T newValue) { - int len = content.length; - Object[] newContent = new Object[len + 1]; - System.arraycopy(content, 0, newContent, 1, len); - newContent[0] = newValue; - return new ImmutableList(newContent); - } - - public int indexOf(T val) { - int len = content.length; - if (val == null) { - for (int i = 0; i < len; i++) if (content[i] == null) return i; - return -1; - } - - for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; - return -1; - } - - public ImmutableList removeElement(T val) { - int idx = indexOf(val); - return idx == -1 ? this : removeElementAt(idx); - } - - public ImmutableList removeElementAt(int idx) { - int len = content.length; - Object[] newContent = new Object[len - 1]; - if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); - if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); - return new ImmutableList(newContent); - } - - public boolean isEmpty() { - return content.length == 0; - } - - public int size() { - return content.length; - } - - @SuppressWarnings("unchecked") - public T get(int idx) { - return (T) content[idx]; - } - - public boolean contains(T in) { - if (in == null) { - for (Object e : content) if (e == null) return true; - return false; - } - - for (Object e : content) if (in.equals(e)) return true; - return false; - } - - public Iterator iterator() { - return new Iterator() { - private int idx = 0; - @Override public boolean hasNext() { - return idx < content.length; - } - - @SuppressWarnings("unchecked") - @Override public T next() { - if (idx < content.length) return (T) content[idx++]; - throw new NoSuchElementException(); - } - - @Override public void remove() { - throw new UnsupportedOperationException("List is immutable"); - } - }; - } - - @Override public String toString() { - return Arrays.toString(content); - } - - @Override public boolean equals(Object obj) { - if (!(obj instanceof ImmutableList)) return false; - if (obj == this) return true; - return Arrays.equals(content, ((ImmutableList) obj).content); - } - - @Override public int hashCode() { - return Arrays.hashCode(content); - } -} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java index dfec8815..cbe90eed 100644 --- a/src/utils/lombok/core/JavaIdentifiers.java +++ b/src/utils/lombok/core/JavaIdentifiers.java @@ -27,7 +27,7 @@ package lombok.core; public class JavaIdentifiers { private JavaIdentifiers() {} - private static final ImmutableList KEYWORDS = ImmutableList.of( + private static final LombokImmutableList KEYWORDS = LombokImmutableList.of( "public", "private", "protected", "default", "switch", "case", "for", "do", "goto", "const", "strictfp", "while", "if", "else", diff --git a/src/utils/lombok/core/LombokImmutableList.java b/src/utils/lombok/core/LombokImmutableList.java new file mode 100644 index 00000000..e0e1136c --- /dev/null +++ b/src/utils/lombok/core/LombokImmutableList.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 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.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public final class LombokImmutableList implements Iterable { + private Object[] content; + private static final LombokImmutableList EMPTY = new LombokImmutableList(new Object[0]); + + @SuppressWarnings("unchecked") + public static LombokImmutableList of() { + return (LombokImmutableList) EMPTY; + } + + public static LombokImmutableList of(T a) { + return new LombokImmutableList(new Object[] {a}); + } + + public static LombokImmutableList of(T a, T b) { + return new LombokImmutableList(new Object[] {a, b}); + } + + public static LombokImmutableList of(T a, T b, T c) { + return new LombokImmutableList(new Object[] {a, b, c}); + } + + public static LombokImmutableList of(T a, T b, T c, T d) { + return new LombokImmutableList(new Object[] {a, b, c, d}); + } + + public static LombokImmutableList of(T a, T b, T c, T d, T e) { + return new LombokImmutableList(new Object[] {a, b, c, d, e}); + } + + public static LombokImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { + Object[] rest = g == null ? new Object[] {null} : g; + Object[] val = new Object[rest.length + 6]; + System.arraycopy(rest, 0, val, 6, rest.length); + val[0] = a; + val[1] = b; + val[2] = c; + val[3] = d; + val[4] = e; + val[5] = f; + return new LombokImmutableList(val); + } + + public static LombokImmutableList copyOf(Collection list) { + return new LombokImmutableList(list.toArray()); + } + + public static LombokImmutableList copyOf(Iterable iterable) { + List list = new ArrayList(); + for (T o : iterable) list.add(o); + return copyOf(list); + } + + private LombokImmutableList(Object[] content) { + this.content = content; + } + + public LombokImmutableList replaceElementAt(int idx, T newValue) { + Object[] newContent = content.clone(); + newContent[idx] = newValue; + return new LombokImmutableList(newContent); + } + + public LombokImmutableList append(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 0, len); + newContent[len] = newValue; + return new LombokImmutableList(newContent); + } + + public LombokImmutableList prepend(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 1, len); + newContent[0] = newValue; + return new LombokImmutableList(newContent); + } + + public int indexOf(T val) { + int len = content.length; + if (val == null) { + for (int i = 0; i < len; i++) if (content[i] == null) return i; + return -1; + } + + for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; + return -1; + } + + public LombokImmutableList removeElement(T val) { + int idx = indexOf(val); + return idx == -1 ? this : removeElementAt(idx); + } + + public LombokImmutableList removeElementAt(int idx) { + int len = content.length; + Object[] newContent = new Object[len - 1]; + if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); + if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); + return new LombokImmutableList(newContent); + } + + public boolean isEmpty() { + return content.length == 0; + } + + public int size() { + return content.length; + } + + @SuppressWarnings("unchecked") + public T get(int idx) { + return (T) content[idx]; + } + + public boolean contains(T in) { + if (in == null) { + for (Object e : content) if (e == null) return true; + return false; + } + + for (Object e : content) if (in.equals(e)) return true; + return false; + } + + public Iterator iterator() { + return new Iterator() { + private int idx = 0; + @Override public boolean hasNext() { + return idx < content.length; + } + + @SuppressWarnings("unchecked") + @Override public T next() { + if (idx < content.length) return (T) content[idx++]; + throw new NoSuchElementException(); + } + + @Override public void remove() { + throw new UnsupportedOperationException("List is immutable"); + } + }; + } + + @Override public String toString() { + return Arrays.toString(content); + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof LombokImmutableList)) return false; + if (obj == this) return true; + return Arrays.equals(content, ((LombokImmutableList) obj).content); + } + + @Override public int hashCode() { + return Arrays.hashCode(content); + } +} -- cgit From 7af9add9996f2efab6cccc50c5503b3457534930 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jul 2013 00:51:31 +0200 Subject: * Fixed issues with @FieldDefaults and @Value (you can NOT override @Value's final-by-default and private-by-default with it; now appropriate warnings are emitted) * Builder now errors out on presence of most lombok annotations on an explicit builder class. * Builder now takes @FieldDefaults/@Value into account. * Builder on type now generates the constructor as package private instead of private to avoid synthetic accessor constructors. * added a bunch of test cases. * added a test case feature: If the expected file is omitted entirely but there are expected messages, the differences in the output itself are ignored. * streamlined checking for boolean-ness (removed some duplicate code) * added 'fluent' and 'chain' to @Builder. --- src/core/lombok/core/TransformationsUtil.java | 25 ++++++++++++++-- .../eclipse/handlers/EclipseHandlerUtil.java | 35 ++++++++++++++++++++-- .../lombok/eclipse/handlers/HandleBuilder.java | 30 +++++++++++++++---- .../eclipse/handlers/HandleFieldDefaults.java | 10 ++++++- src/core/lombok/eclipse/handlers/HandleGetter.java | 2 +- src/core/lombok/eclipse/handlers/HandleSetter.java | 2 +- src/core/lombok/eclipse/handlers/HandleWither.java | 2 +- src/core/lombok/experimental/Builder.java | 16 ++++++++++ src/core/lombok/javac/handlers/HandleBuilder.java | 33 +++++++++++++++----- .../lombok/javac/handlers/HandleFieldDefaults.java | 10 ++++++- .../lombok/javac/handlers/JavacHandlerUtil.java | 29 +++++++++++++++++- test/core/src/lombok/AbstractRunTests.java | 18 ++++++----- .../after-delombok/BuilderChainAndFluent.java | 31 +++++++++++++++++++ .../resource/after-delombok/BuilderSimple.java | 2 +- .../resource/after-ecj/BuilderChainAndFluent.java | 25 ++++++++++++++++ .../resource/after-ecj/BuilderSimple.java | 2 +- .../resource/before/BuilderChainAndFluent.java | 4 +++ .../resource/before/BuilderInvalidUse.java | 18 +++++++++++ .../BuilderInvalidUse.java.messages | 2 ++ .../messages-ecj/BuilderInvalidUse.java.messages | 2 ++ website/features/Value.html | 5 +++- website/features/experimental/Builder.html | 33 ++++++++++++-------- website/features/experimental/index.html | 2 +- 23 files changed, 290 insertions(+), 48 deletions(-) create mode 100644 test/transform/resource/after-delombok/BuilderChainAndFluent.java create mode 100644 test/transform/resource/after-ecj/BuilderChainAndFluent.java create mode 100644 test/transform/resource/before/BuilderChainAndFluent.java create mode 100644 test/transform/resource/before/BuilderInvalidUse.java create mode 100644 test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages create mode 100644 test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java index 921c27d6..8959ad7a 100644 --- a/src/core/lombok/core/TransformationsUtil.java +++ b/src/core/lombok/core/TransformationsUtil.java @@ -22,13 +22,25 @@ 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; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.Value; import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.experimental.Wither; /** * Container for static utility methods useful for some of the standard lombok transformations, regardless of @@ -39,6 +51,13 @@ public class TransformationsUtil { //Prevent instantiation } + @SuppressWarnings({"all", "unchecked", "deprecation"}) + public static final List> INVALID_ON_BUILDERS = Collections.unmodifiableList( + Arrays.>asList( + Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class, + RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class, + Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class)); + /** * Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list. * For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if @@ -159,12 +178,12 @@ public class TransformationsUtil { if (fieldName.length() == 0) return null; - Accessors ac = accessors.getInstance(); - fieldName = removePrefix(fieldName, ac.prefix()); + Accessors ac = accessors == null ? null : accessors.getInstance(); + fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix()); if (fieldName == null) return null; String fName = fieldName.toString(); - if (adhereToFluent && ac.fluent()) return fName; + if (adhereToFluent && ac != null && ac.fluent()) return fName; if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) { // The field is for example named 'isRunning'. diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 364ce0a5..9bd634f7 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import static lombok.core.TransformationsUtil.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -296,6 +297,29 @@ public class EclipseHandlerUtil { } + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { + List disallowed = null; + for (EclipseNode child : typeNode.down()) { + for (Class annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + if (disallowed == null) disallowed = new ArrayList(); + disallowed.add(annType.getSimpleName()); + } + } + } + + int size = disallowed == null ? 0 : disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + public static Annotation copyAnnotation(Annotation annotation, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; @@ -845,15 +869,20 @@ public class EclipseHandlerUtil { private static final Object MARKER = new Object(); static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) { - if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return; - generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + if (isBoolean(returnType)) { + generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + } + } + + public static boolean isBoolean(TypeReference typeReference) { + return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0; } private static GetterMethod findGetter(EclipseNode field) { FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get(); boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration); TypeReference fieldType = fieldDeclaration.type; - boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0); + boolean isBoolean = forceBool || isBoolean(fieldType); EclipseNode typeNode = field.up(); for (String potentialGetterName : toAllGetterNames(field, isBoolean)) { diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index e2bf5fe2..70110a9c 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -58,13 +58,17 @@ import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.Builder; +import lombok.experimental.NonFinal; @ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; @@ -99,14 +103,23 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); - new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.emptyList(), ast); + List fields = new ArrayList(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; namesOfParameters.add(fd.name); typesOfParameters.add(fd.type); + fields.add(fieldNode); } + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.emptyList(), ast); + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; thrownExceptions = null; @@ -181,11 +194,15 @@ public class HandleBuilder extends EclipseAnnotationHandler { } EclipseNode builderType = findInnerClass(tdParent, builderClassName); - if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); List newMethods = new ArrayList(); for (EclipseNode fieldNode : fieldNodes) { - MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast); + MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); if (newMethod != null) newMethods.add(newMethod); } @@ -315,7 +332,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { private static final AbstractMethodDeclaration[] EMPTY = {}; - private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source) { + private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) { TypeDeclaration td = (TypeDeclaration) builderType.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY; @@ -329,7 +346,10 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (Arrays.equals(name, existingName)) return null; } - return HandleSetter.createSetter(td, fieldNode, fieldNode.getName(), true, ClassFileConstants.AccPublic, + boolean isBoolean = isBoolean(fd.type); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, source, Collections.emptyList(), Collections.emptyList()); } diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 0d21fc08..d6d839cc 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) -@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends EclipseAnnotationHandler { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { @@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 760c595e..787f6f6c 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -187,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler { } TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String getterName = toGetterName(fieldNode, isBoolean); if (getterName == null) { diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index ae846a4e..3bfcc51c 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String setterName = toSetterName(fieldNode, isBoolean); boolean shouldReturnThis = shouldReturnThis(fieldNode); diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 9d74cbd1..27fbc635 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String witherName = toWitherName(fieldNode, isBoolean); if (witherName == null) { diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java index 5f2d1ca6..1300e7d3 100644 --- a/src/core/lombok/experimental/Builder.java +++ b/src/core/lombok/experimental/Builder.java @@ -118,4 +118,20 @@ public @interface Builder { * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}. */ String builderClassName() default ""; + + /** + * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this + * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}. + *

+ * Default: true + */ + boolean fluent() default true; + + /** + * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain + * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead. + *

+ * Default: true + */ + boolean chain() default true; } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index aa485b26..6422f5ed 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -49,16 +49,19 @@ import com.sun.tools.javac.util.Name; import lombok.AccessLevel; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; import lombok.experimental.Builder; +import lombok.experimental.NonFinal; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; - import static lombok.javac.Javac.*; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; @ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { Builder builderInstance = annotation.getInstance(); @@ -94,14 +97,22 @@ public class HandleBuilder extends JavacAnnotationHandler { if (parent.get() instanceof JCClassDecl) { tdParent = parent; JCClassDecl td = (JCClassDecl) tdParent.get(); - new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); - + ListBuffer allFields = ListBuffer.lb(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) { JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; namesOfParameters.add(fd.name); typesOfParameters.add(fd.vartype); + allFields.append(fieldNode); } + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode); + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; thrownExceptions = List.nil(); @@ -171,11 +182,15 @@ public class HandleBuilder extends JavacAnnotationHandler { } JavacNode builderType = findInnerClass(tdParent, builderClassName); - if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } java.util.List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); java.util.List newMethods = new ArrayList(); for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast); + JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); if (newMethod != null) newMethods.add(newMethod); } @@ -281,16 +296,20 @@ public class HandleBuilder extends JavacAnnotationHandler { } - private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) { + private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source, boolean fluent, boolean chain) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + for (JavacNode child : builderType.down()) { if (child.getKind() != Kind.METHOD) continue; Name existingName = ((JCMethodDecl) child.get()).name; if (existingName.equals(fieldName)) return null; } + boolean isBoolean = isBoolean(fieldNode); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + TreeMaker maker = builderType.getTreeMaker(); - return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.nil(), List.nil()); + return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.nil(), List.nil()); } private JavacNode findInnerClass(JavacNode parent, String name) { diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index d32446c3..038f3e3f 100644 --- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(JavacAnnotationHandler.class) -@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends JavacAnnotationHandler { public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { @@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler { return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index a24dad7d..d7d29da2 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -21,6 +21,7 @@ */ package lombok.javac.handlers; +import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS; import static lombok.javac.Javac.*; import java.lang.annotation.Annotation; @@ -446,8 +447,12 @@ public class JavacHandlerUtil { } } - private static boolean isBoolean(JavacNode field) { + public static boolean isBoolean(JavacNode field) { JCExpression varType = ((JCVariableDecl) field.get()).vartype; + return isBoolean(varType); + } + + public static boolean isBoolean(JCExpression varType) { return varType != null && varType.toString().equals("boolean"); } @@ -1065,6 +1070,28 @@ public class JavacHandlerUtil { return maker.Ident(typeName); } + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { + List disallowed = List.nil(); + for (JavacNode child : typeNode.down()) { + for (Class annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + disallowed = disallowed.append(annType.getSimpleName()); + } + } + } + + int size = disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.head + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + static List copyAnnotations(List in) { ListBuffer out = ListBuffer.lb(); for (JCExpression expr : in) { diff --git a/test/core/src/lombok/AbstractRunTests.java b/test/core/src/lombok/AbstractRunTests.java index a80c7d8d..2f3f0988 100644 --- a/test/core/src/lombok/AbstractRunTests.java +++ b/test/core/src/lombok/AbstractRunTests.java @@ -68,10 +68,12 @@ public abstract class AbstractRunTests { } } - StringReader r = new StringReader(expectedFile); - BufferedReader br = new BufferedReader(r); - String firstLine = br.readLine(); - if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false; + if (expectedFile != null) { + StringReader r = new StringReader(expectedFile); + BufferedReader br = new BufferedReader(r); + String firstLine = br.readLine(); + if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false; + } compare( file.getName(), @@ -91,7 +93,7 @@ public abstract class AbstractRunTests { try { reader = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e) { - return ""; + return null; } StringBuilder result = new StringBuilder(); String line; @@ -104,7 +106,7 @@ public abstract class AbstractRunTests { } private String readFile(File dir, File file, boolean messages) throws IOException { - if (dir == null) return ""; + if (dir == null) return null; return readFile(new File(dir, file.getName() + (messages ? ".messages" : ""))); } @@ -140,7 +142,9 @@ public abstract class AbstractRunTests { } private void compare(String name, String expectedFile, String actualFile, List expectedMessages, LinkedHashSet actualMessages, boolean printErrors) throws Throwable { - try { + if (expectedFile == null && expectedMessages.isEmpty()) expectedFile = ""; + + if (expectedFile != null) try { compareContent(name, expectedFile, actualFile); } catch (Throwable e) { if (printErrors) { diff --git a/test/transform/resource/after-delombok/BuilderChainAndFluent.java b/test/transform/resource/after-delombok/BuilderChainAndFluent.java new file mode 100644 index 00000000..d4975bff --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderChainAndFluent.java @@ -0,0 +1,31 @@ +class BuilderChainAndFluent { + private final int yes; + @java.lang.SuppressWarnings("all") + BuilderChainAndFluent(final int yes) { + this.yes = yes; + } + @java.lang.SuppressWarnings("all") + public static class BuilderChainAndFluentBuilder { + private int yes; + @java.lang.SuppressWarnings("all") + BuilderChainAndFluentBuilder() { + } + @java.lang.SuppressWarnings("all") + public void setYes(final int yes) { + this.yes = yes; + } + @java.lang.SuppressWarnings("all") + public BuilderChainAndFluent build() { + return new BuilderChainAndFluent(yes); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static BuilderChainAndFluentBuilder builder() { + return new BuilderChainAndFluentBuilder(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java index 24ac369c..11c0e58c 100644 --- a/test/transform/resource/after-delombok/BuilderSimple.java +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -5,7 +5,7 @@ class BuilderSimple { private List also; private int $butNotMe; @java.lang.SuppressWarnings("all") - private BuilderSimple(final int yes, final List also) { + BuilderSimple(final int yes, final List also) { this.yes = yes; this.also = also; } diff --git a/test/transform/resource/after-ecj/BuilderChainAndFluent.java b/test/transform/resource/after-ecj/BuilderChainAndFluent.java new file mode 100644 index 00000000..6a307105 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderChainAndFluent.java @@ -0,0 +1,25 @@ +@lombok.experimental.Builder(fluent = false,chain = false) class BuilderChainAndFluent { + public static @java.lang.SuppressWarnings("all") class BuilderChainAndFluentBuilder { + private int yes; + @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") void setYes(final int yes) { + this.yes = yes; + } + public @java.lang.SuppressWarnings("all") BuilderChainAndFluent build() { + return new BuilderChainAndFluent(yes); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes) + ")"); + } + } + private final int yes; + @java.lang.SuppressWarnings("all") BuilderChainAndFluent(final int yes) { + super(); + this.yes = yes; + } + public static @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder builder() { + return new BuilderChainAndFluentBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java index 228b1928..85db360d 100644 --- a/test/transform/resource/after-ecj/BuilderSimple.java +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -25,7 +25,7 @@ import java.util.List; private final int yes; private List also; private int $butNotMe; - private @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List also) { + @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List also) { super(); this.yes = yes; this.also = also; diff --git a/test/transform/resource/before/BuilderChainAndFluent.java b/test/transform/resource/before/BuilderChainAndFluent.java new file mode 100644 index 00000000..4d08741b --- /dev/null +++ b/test/transform/resource/before/BuilderChainAndFluent.java @@ -0,0 +1,4 @@ +@lombok.experimental.Builder(fluent = false, chain = false) +class BuilderChainAndFluent { + private final int yes; +} diff --git a/test/transform/resource/before/BuilderInvalidUse.java b/test/transform/resource/before/BuilderInvalidUse.java new file mode 100644 index 00000000..07f37d3d --- /dev/null +++ b/test/transform/resource/before/BuilderInvalidUse.java @@ -0,0 +1,18 @@ +@lombok.experimental.Builder +class BuilderInvalidUse { + private int something; + + @lombok.Getter @lombok.Setter @lombok.experimental.FieldDefaults(makeFinal = true) @lombok.experimental.Wither @lombok.Data @lombok.ToString @lombok.EqualsAndHashCode + @lombok.AllArgsConstructor + public static class BuilderInvalidUseBuilder { + + } +} + +@lombok.experimental.Builder +class AlsoInvalid { + @lombok.Value + public static class AlsoInvalidBuilder { + + } +} \ No newline at end of file diff --git a/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages new file mode 100644 index 00000000..aeeb0c86 --- /dev/null +++ b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages @@ -0,0 +1,2 @@ +1:1 @Getter, @Setter, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes. +12:1 @Value is not allowed on builder classes. \ No newline at end of file diff --git a/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages new file mode 100644 index 00000000..8ffc6e26 --- /dev/null +++ b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages @@ -0,0 +1,2 @@ +1:0 @Getter, @Setter, @FieldDefaults, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes. +12:331 @Value is not allowed on builder classes. \ No newline at end of file diff --git a/website/features/Value.html b/website/features/Value.html index 92fcc825..e2cd8600 100644 --- a/website/features/Value.html +++ b/website/features/Value.html @@ -31,7 +31,7 @@

It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
It is possible to override any default behaviour for any of the 'parts' that make up @Value by explicitly using that annotation. -

+

@@ -54,6 +54,9 @@

@Value was an experimental feature from v0.11.4 to v0.11.9 (as @lombok.experimental.Value). It has since been moved into the core package. The old annotation is still around (and is an alias). It will eventually be removed in a future version, though. +

+ It is not possible to use @FieldDefaults to 'undo' the private-by-default and final-by-default aspect of fields in the annotated class. Use @NonFinal and @PackagePrivate on the fields in the class to override this behaviour. +

diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 31fcd5ad..16d58050 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -23,7 +23,7 @@ as a core feature and move out of the experimental package.
@Builder
-
It's like drinking tea with an extended pinky while wearing a monocle: No-hassle fancy-pants APIs for object creation!
+
... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!
@Accessors
A more fluent API for getters and setters.
@ExtensionMethod
-- cgit From e72ab866d68d80a6d4c8136dcbf5d0e5d9617cd0 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jul 2013 00:58:05 +0200 Subject: bumped version --- doc/changelog.markdown | 2 +- src/core/lombok/core/Version.java | 4 ++-- website/features/Value.html | 2 +- website/features/experimental/Builder.html | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 95c31db2..0e15e7e2 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,7 +1,7 @@ Lombok Changelog ---------------- -### v0.11.9 (Edgy Guinea Pig) +### v0.12.0 "Angry Butterfly" (July 16th, 2013) * FEATURE: javadoc on fields will now be copied to generated getters / setters / withers. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). [@Wither documentation](http://projectlombok.org/features/experimental/Wither.html). * CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index 7909a5a1..887d57fa 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -28,8 +28,8 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "0.11.9"; - private static final String RELEASE_NAME = "Dashing Kakapo"; + private static final String VERSION = "0.12.0"; + private static final String RELEASE_NAME = "Angry Butterfy"; private Version() { //Prevent instantiation diff --git a/website/features/Value.html b/website/features/Value.html index e2cd8600..6bbd6f2a 100644 --- a/website/features/Value.html +++ b/website/features/Value.html @@ -19,7 +19,7 @@

@Value no longer implies @Wither since lombok v0.11.8.

- @Value promoted to the main lombok package since lombok v0.11.10. + @Value promoted to the main lombok package since lombok v0.12.0.

Overview

diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html index a43d024b..0616afc9 100644 --- a/website/features/experimental/Builder.html +++ b/website/features/experimental/Builder.html @@ -15,7 +15,7 @@

Since

- @Builder was introduced as experimental feature in lombok v0.11.10. + @Builder was introduced as experimental feature in lombok v0.12.0.

-- cgit From 023d90bc75517d478e7f0f9c063ccbfa135fe62b Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jul 2013 02:06:45 +0200 Subject: post-release version bump to 0.12.1 --- src/core/lombok/core/Version.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index 887d57fa..bd83c1f8 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -28,7 +28,7 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "0.12.0"; + private static final String VERSION = "0.12.1"; private static final String RELEASE_NAME = "Angry Butterfy"; private Version() { -- cgit From deed98be16e5099af52d951fc611f86a82a42858 Mon Sep 17 00:00:00 2001 From: Roel Spilker Date: Thu, 18 Jul 2013 16:27:50 +0200 Subject: Upyeared the copyright notice in the generated javadoc. --- buildScripts/website.ant.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index baf869a4..b9546ee9 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -333,7 +333,7 @@ such as converting the changelog into HTML, and creating javadoc.
Lombok - ]]>v${lombok.version}
- Copyright © 2009-2011 The Project Lombok Authors, licensed under the MIT licence.]]> + Copyright © 2009-2013 The Project Lombok Authors, licensed under the MIT licence.]]> -- cgit
Maven or Ivy
Maven or Ivy Lombok is in maven central. More…
Javac
NetBeans Just put lombok.jar on the classpath and enable annotation processing. More…
Eclipse and STSRun lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project.
Eclipse and variantsRun lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project. Supported variants: Springsource Tool Suite, JBoss Developer Studio
IDEA IntelliJ
IDEA IntelliJ lombok doesn't (yet) work under IntelliJ. We're working on it though!
Javadoc
GWT Lombok works with GWT. More…
Play! Framework
Play! Framework Use Aaron Freeman's lombok play plugin.
ecjRun lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project. Supported variants: Springsource Tool Suite, JBoss Developer Studio
IDEA IntelliJlombok doesn't (yet) work under IntelliJ. We're working on it though!
A plugin developed by Michael Plushnikov adds support for most features.
Javadoc First delombok your code then run javadoc on the result. More…