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. --- website/features/experimental/Accessors.html | 2 +- website/features/experimental/Builder.html | 127 +++++++++++++++++++++++++++ website/features/experimental/index.html | 2 + 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 website/features/experimental/Builder.html (limited to 'website/features/experimental') 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 (limited to 'website/features/experimental') 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 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 (limited to 'website/features/experimental') 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 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 (limited to 'website/features/experimental') 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(-) (limited to 'website/features/experimental') 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