From 56150952c451f0d8c2018424191d4480ac5e8460 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 22:04:27 +0100 Subject: Added @Log4j2 support. --- doc/changelog.markdown | 1 + 1 file changed, 1 insertion(+) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index a415cd01..3b56aa00 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -4,6 +4,7 @@ Lombok Changelog ### v0.11.7 (Edgy Guinea Pig) * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) +* FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. -- cgit From c0daffdb438e1ded5f96d3425b23067c66042a1c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 11 Mar 2013 23:30:20 +0100 Subject: Updated documentation to reflect support of JBoss Developer Studio --- doc/changelog.markdown | 1 + website/download.html | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 3b56aa00..401349db 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -5,6 +5,7 @@ Lombok Changelog * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) +* ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/website/download.html b/website/download.html index bb354c8f..dea43799 100644 --- a/website/download.html +++ b/website/download.html @@ -44,7 +44,7 @@ - + @@ -53,10 +53,10 @@ - - + + - + @@ -68,7 +68,7 @@ - + -- cgit From 9400f39d12813740634bba233aacc6edcf62c584 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 12 Mar 2013 00:25:50 +0100 Subject: Fix for issue #436: In eclipse, calling i.e. `list.get(i).extensionMethod()` would fail with VerifyError when that code is executed in eclipse. --- doc/changelog.markdown | 1 + src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 401349db..a3ac5ea4 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -6,6 +6,7 @@ Lombok Changelog * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) * ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) +* BUGFIX: `@ExtensionMethods` no longer causes `VerifyError` exceptions when running eclipse-compiled code if extension methods are called on expressions which are method calls whose return type is a type variable. For example, `someList.get(i).extensionMethod()` would fail that way. [Issue #436](http://code.google.com/p/projectlombok/issues/detail?id=436) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index 008e722a..a3d77055 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java @@ -236,10 +236,8 @@ public class PatchExtensionMethod { for (int i = 0, iend = arguments.size(); i < iend; i++) { Expression arg = arguments.get(i); if (fixedBinding.parameters[i].isArrayType() != arg.resolvedType.isArrayType()) break; - if (arg.resolvedType.isArrayType()) { - if (arg instanceof MessageSend) { - ((MessageSend) arg).valueCast = arg.resolvedType; - } + if (arg instanceof MessageSend) { + ((MessageSend) arg).valueCast = arg.resolvedType; } if (!fixedBinding.parameters[i].isBaseType() && arg.resolvedType.isBaseType()) { int id = arg.resolvedType.id; -- cgit From 66f32d5073bc726f76f958471ea93ec5a29c354b Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 18 Mar 2013 23:57:09 +0100 Subject: Fixed issue 459: Delombok would choke on try-with-resources. --- doc/changelog.markdown | 1 + .../lombok/delombok/PrettyCommentsPrinter.java | 28 ++++++++++++++++++++++ test/pretty/resource/after/TryWithResources.java | 9 +++++++ test/pretty/resource/before/TryWithResources.java | 9 +++++++ 4 files changed, 47 insertions(+) create mode 100644 test/pretty/resource/after/TryWithResources.java create mode 100644 test/pretty/resource/before/TryWithResources.java (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index a3ac5ea4..043e524e 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -7,6 +7,7 @@ Lombok Changelog * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) * ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) * BUGFIX: `@ExtensionMethods` no longer causes `VerifyError` exceptions when running eclipse-compiled code if extension methods are called on expressions which are method calls whose return type is a type variable. For example, `someList.get(i).extensionMethod()` would fail that way. [Issue #436](http://code.google.com/p/projectlombok/issues/detail?id=436) +* BUGFIX: java 7's try-with-resources statement did not delombok correctly. [Issue #459](http://code.google.com/p/projectlombok/issues/detail?id=459) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 6e9a1c94..9c6a2bd7 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -22,6 +22,11 @@ * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ + +/* + * Code derived from com.sun.tools.javac.tree.Pretty, from the langtools project. + * A version can be found at, for example, http://hg.openjdk.java.net/jdk7/build/langtools + */ package lombok.delombok; import static com.sun.tools.javac.code.Flags.ANNOTATION; @@ -981,6 +986,29 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitTry(JCTry tree) { try { print("try "); + List resources = null; + try { + Field f = JCTry.class.getField("resources"); + resources = (List) f.get(tree); + } catch (Exception ignore) { + // In JDK6 and down this field does not exist; resources will retain its initializer value which is what we want. + } + + if (resources != null && resources.nonEmpty()) { + boolean first = true; + print("("); + for (Object var0 : resources) { + JCTree var = (JCTree) var0; + if (!first) { + println(); + indent(); + } + printStat(var); + first = false; + } + print(") "); + } + printStat(tree.body); for (List l = tree.catchers; l.nonEmpty(); l = l.tail) { printStat(l.head); diff --git a/test/pretty/resource/after/TryWithResources.java b/test/pretty/resource/after/TryWithResources.java new file mode 100644 index 00000000..1a8b82e2 --- /dev/null +++ b/test/pretty/resource/after/TryWithResources.java @@ -0,0 +1,9 @@ +//version 7: +import java.io.PrintWriter; +public class TryWithResources { + { + try (final PrintWriter pw = new PrintWriter(System.out);) { + pw.println(); + } + } +} diff --git a/test/pretty/resource/before/TryWithResources.java b/test/pretty/resource/before/TryWithResources.java new file mode 100644 index 00000000..eb622f2c --- /dev/null +++ b/test/pretty/resource/before/TryWithResources.java @@ -0,0 +1,9 @@ +//version 7: +import java.io.PrintWriter; +public class TryWithResources { + { + try (PrintWriter pw = new PrintWriter(System.out)) { + pw.println(); + } + } +} -- cgit From 9538adc161f4d275a42a8f959f015ec30c801537 Mon Sep 17 00:00:00 2001 From: Roel Spilker Date: Tue, 26 Mar 2013 02:44:48 +0100 Subject: updated changelog to reflect performance improvements of previous commit. --- doc/changelog.markdown | 1 + 1 file changed, 1 insertion(+) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 043e524e..ccbb22dd 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.7 (Edgy Guinea Pig) +* FEATURE: Major performance improvements in eclipse by profiling the project clean process. * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) * FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) -- cgit From ea47245b39c784deed8f07b33d9e828b947fc6cc Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 23 Apr 2013 03:26:16 +0200 Subject: prerelease version bump --- doc/changelog.markdown | 2 +- src/core/lombok/core/Version.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index ccbb22dd..15e3c1e0 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,7 +1,7 @@ Lombok Changelog ---------------- -### v0.11.7 (Edgy Guinea Pig) +### v0.11.8 (April 23rd, 2013) * FEATURE: Major performance improvements in eclipse by profiling the project clean process. * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index b9239978..4be1d1f4 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -28,7 +28,7 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "0.11.7"; + private static final String VERSION = "0.11.8"; private static final String RELEASE_NAME = "Dashing Kakapo"; private Version() { -- cgit From f98bf919cc6701e98087d39fefb7bbfc85688834 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 6 May 2013 22:09:13 +0200 Subject: Fixed issue 513: If equals is present but hashCode isn't, @Data now generates a warning to explain this strange situation. --- doc/changelog.markdown | 3 + .../eclipse/handlers/HandleEqualsAndHashCode.java | 23 ++++-- .../javac/handlers/HandleEqualsAndHashCode.java | 22 ++++-- .../EqualsAndHashCodeWithSomeExistingMethods.java | 82 ++++++++++++++++++++++ .../EqualsAndHashCodeWithSomeExistingMethods.java | 71 +++++++++++++++++++ .../EqualsAndHashCodeWithSomeExistingMethods.java | 47 +++++++++++++ ...ndHashCodeWithSomeExistingMethods.java.messages | 2 + ...ndHashCodeWithSomeExistingMethods.java.messages | 2 + 8 files changed, 239 insertions(+), 13 deletions(-) create mode 100644 test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java create mode 100644 test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java create mode 100644 test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java create mode 100644 test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages create mode 100644 test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 15e3c1e0..32af72d3 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,6 +1,9 @@ Lombok Changelog ---------------- +### v0.11.9 (Edgy Guinea Pig) +* BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) + ### v0.11.8 (April 23rd, 2013) * FEATURE: Major performance improvements in eclipse by profiling the project clean process. * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 0c82b74c..6990e609 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.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 @@ -204,18 +204,27 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler existsResults = new ArrayList(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); errorNode.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + errorNode.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 5f69be9d..3b1e226f 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.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 @@ -25,6 +25,7 @@ import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import lombok.EqualsAndHashCode; @@ -178,17 +179,26 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler existsResults = new ArrayList(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); source.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + source.addWarning(msg); } return; case NOT_EXISTS: diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..0a6b1e7f --- /dev/null +++ b/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,82 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; +class EqualsAndHashCodeWithSomeExistingMethods { + int x; + public int hashCode() { + return 42; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithSomeExistingMethods() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithSomeExistingMethods(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + public boolean canEqual(Object other) { + return false; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithSomeExistingMethods2() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithSomeExistingMethods2(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithAllExistingMethods { + int x; + public int hashCode() { + return 42; + } + public boolean equals(Object other) { + return false; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithAllExistingMethods() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithAllExistingMethods(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithNoExistingMethods { + int x; + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithNoExistingMethods() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeWithNoExistingMethods)) return false; + final EqualsAndHashCodeWithNoExistingMethods other = (EqualsAndHashCodeWithNoExistingMethods)o; + if (!other.canEqual((java.lang.Object)this)) return false; + if (this.x != other.x) return false; + return true; + } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeWithNoExistingMethods; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + this.x; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithNoExistingMethods(x=" + this.x + ")"; + } +} diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..cdd771a4 --- /dev/null +++ b/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,71 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithSomeExistingMethods { + int x; + public int hashCode() { + return 42; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithSomeExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithSomeExistingMethods() { + super(); + } +} +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + public boolean canEqual(Object other) { + return false; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithSomeExistingMethods2(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithSomeExistingMethods2() { + super(); + } +} +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithAllExistingMethods { + int x; + public int hashCode() { + return 42; + } + public boolean equals(Object other) { + return false; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithAllExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithAllExistingMethods() { + super(); + } +} +@Data @Getter(AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) class EqualsAndHashCodeWithNoExistingMethods { + int x; + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeWithNoExistingMethods))) + return false; + final @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithNoExistingMethods other = (EqualsAndHashCodeWithNoExistingMethods) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((this.x != other.x)) + return false; + return true; + } + public @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeWithNoExistingMethods); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + this.x); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithNoExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithNoExistingMethods() { + super(); + } +} diff --git a/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..784e3b3f --- /dev/null +++ b/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,47 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithSomeExistingMethods { + int x; + + public int hashCode() { + return 42; + } +} + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + + public boolean canEqual(Object other) { + return false; + } +} + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithAllExistingMethods { + int x; + + public int hashCode() { + return 42; + } + + public boolean equals(Object other) { + return false; + } +} + +@Data +@Getter(AccessLevel.NONE) +@Setter(lombok.AccessLevel.NONE) +class EqualsAndHashCodeWithNoExistingMethods { + int x; +} + diff --git a/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages new file mode 100644 index 00000000..9a0b29f3 --- /dev/null +++ b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages @@ -0,0 +1,2 @@ +4:1 Not generating equals: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). +15:1 Not generating equals and hashCode: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). diff --git a/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages new file mode 100644 index 00000000..cf6ebea2 --- /dev/null +++ b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages @@ -0,0 +1,2 @@ +4:57 Not generating equals: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). +15:194 Not generating equals and hashCode: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). -- cgit From 5d6f82d799178f8a15f635f0cac6f733ed310d17 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 24 May 2013 00:54:38 +0200 Subject: added previous fix of issue 520 to the changelog --- doc/changelog.markdown | 1 + 1 file changed, 1 insertion(+) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 32af72d3..3eb8b5f5 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) ### v0.11.8 (April 23rd, 2013) -- cgit From 7bbb7cf3ca25cb8727a6ec226de1ed1fc5bf47e9 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 28 May 2013 13:03:54 +0200 Subject: Fixes for issue 470: VerifyErrors when using @SneakyThrows. --- doc/changelog.markdown | 1 + .../bytecode/PreventNullAnalysisRemover.java | 2 +- src/core/lombok/bytecode/SneakyThrowsRemover.java | 115 ++++++++++++++++++++- src/core/lombok/core/PostCompiler.java | 4 +- src/core/lombok/eclipse/EclipseAST.java | 15 +-- src/core/lombok/eclipse/EclipseAstProblemView.java | 9 +- src/core/lombok/eclipse/TransformEclipseAST.java | 2 +- .../eclipse/handlers/EclipseHandlerUtil.java | 2 +- .../lombok/eclipse/agent/EclipsePatcher.java | 2 +- .../lombok/eclipse/agent/PatchFixes.java | 7 +- 10 files changed, 131 insertions(+), 28 deletions(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 3eb8b5f5..e2e3d6b5 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) diff --git a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java index 3342eacb..751e691f 100644 --- a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java +++ b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java @@ -44,7 +44,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new FixedClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java index c54495d4..914c313a 100644 --- a/src/core/lombok/bytecode/SneakyThrowsRemover.java +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,19 +32,21 @@ import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @ProviderFor(PostCompilerTransformation.class) public class SneakyThrowsRemover implements PostCompilerTransformation { - @Override public byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + @Override public byte[] applyTransformations(byte[] original, String fileName, final DiagnosticsReceiver diagnostics) { if (!new ClassFileMetaData(original).usesMethod("lombok/Lombok", "sneakyThrow")) return null; byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new ClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); @@ -53,6 +55,8 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { super(Opcodes.ASM4, mv); } + private boolean methodInsnQueued = false; + @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { if ( opcode == Opcodes.INVOKESTATIC && @@ -60,16 +64,117 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { "lombok/Lombok".equals(owner) && "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;".equals(desc)) { - changesMade.set(true); if (System.getProperty("lombok.debugAsmOnly", null) != null) { super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470! } else { - super.visitInsn(Opcodes.ATHROW); + methodInsnQueued = true; } } else { super.visitMethodInsn(opcode, owner, name, desc); } } + + private void handleQueue() { + if (!methodInsnQueued) return; + super.visitMethodInsn(Opcodes.INVOKESTATIC, "lombok/Lombok", "sneakyThrow", "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;"); + methodInsnQueued = false; + diagnostics.addWarning("Proper usage is: throw lombok.Lombok.sneakyThrow(someException);. You did not 'throw' it. Because of this, the call to sneakyThrow " + + "remains in your classfile and you will need lombok.jar on the classpath at runtime."); + } + + @Override public void visitInsn(int arg0) { + if (methodInsnQueued && arg0 == Opcodes.ATHROW) { + changesMade.set(true); + // As expected, the required ATHROW. We can now safely 'eat' the previous call. + methodInsnQueued = false; + } + handleQueue(); + super.visitInsn(arg0); + } + @Override public void visitFrame(int arg0, int arg1, Object[] arg2, int arg3, Object[] arg4) { + handleQueue(); + super.visitFrame(arg0, arg1, arg2, arg3, arg4); + } + + @Override public void visitIincInsn(int arg0, int arg1) { + handleQueue(); + super.visitIincInsn(arg0, arg1); + } + + @Override public void visitFieldInsn(int arg0, String arg1, String arg2, String arg3) { + handleQueue(); + super.visitFieldInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitIntInsn(int arg0, int arg1) { + handleQueue(); + super.visitIntInsn(arg0, arg1); + } + + @Override public void visitEnd() { + handleQueue(); + super.visitEnd(); + } + + @Override public void visitInvokeDynamicInsn(String arg0, String arg1, Handle arg2, Object... arg3) { + handleQueue(); + super.visitInvokeDynamicInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitLabel(Label arg0) { + handleQueue(); + super.visitLabel(arg0); + } + + @Override public void visitJumpInsn(int arg0, Label arg1) { + handleQueue(); + super.visitJumpInsn(arg0, arg1); + } + + @Override public void visitLdcInsn(Object arg0) { + handleQueue(); + super.visitLdcInsn(arg0); + } + + @Override public void visitLocalVariable(String arg0, String arg1, String arg2, Label arg3, Label arg4, int arg5) { + handleQueue(); + super.visitLocalVariable(arg0, arg1, arg2, arg3, arg4, arg5); + } + + @Override public void visitMaxs(int arg0, int arg1) { + handleQueue(); + super.visitMaxs(arg0, arg1); + } + + @Override public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) { + handleQueue(); + super.visitLookupSwitchInsn(arg0, arg1, arg2); + } + + @Override public void visitMultiANewArrayInsn(String arg0, int arg1) { + handleQueue(); + super.visitMultiANewArrayInsn(arg0, arg1); + } + + @Override public void visitVarInsn(int arg0, int arg1) { + handleQueue(); + super.visitVarInsn(arg0, arg1); + } + + @Override public void visitTryCatchBlock(Label arg0, Label arg1, Label arg2, String arg3) { + handleQueue(); + super.visitTryCatchBlock(arg0, arg1, arg2, arg3); + } + + @Override public void visitTableSwitchInsn(int arg0, int arg1, Label arg2, Label... arg3) { + handleQueue(); + super.visitTableSwitchInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitTypeInsn(int arg0, String arg1) { + handleQueue(); + super.visitTypeInsn(arg0, arg1); + } } reader.accept(new ClassVisitor(Opcodes.ASM4, writer) { diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index 1e40cca2..4a099862 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,6 +33,7 @@ public final class PostCompiler { private static List transformations; public static byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + if (System.getProperty("lombok.disablePostCompiler", null) != null) return original; init(diagnostics); byte[] previous = original; for (PostCompilerTransformation transformation : transformations) { @@ -59,6 +60,7 @@ public final class PostCompiler { } public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException { + if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream; return new ByteArrayOutputStream() { @Override public void close() throws IOException { // no need to call super diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 612dcff7..eed3c0b6 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -24,6 +24,7 @@ package lombok.eclipse; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import org.eclipse.jdt.internal.compiler.CompilationResult; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -123,7 +124,8 @@ public class EclipseAST extends AST { } void addToCompilationResult() { - addProblemToCompilationResult((CompilationUnitDeclaration) top().get(), + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, isWarning, message, sourceStart, sourceEnd); } } @@ -147,11 +149,10 @@ public class EclipseAST extends AST { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; try { - EcjReflectionCheck.addProblemToCompilationResult.invoke(null, ast, isWarning, message, sourceStart, sourceEnd); + EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd); } catch (NoClassDefFoundError e) { //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly //do anything useful here. @@ -168,7 +169,7 @@ public class EclipseAST extends AST { //do anything useful here. } } - + private final CompilationUnitDeclaration compilationUnitDeclaration; private boolean completeParse; @@ -358,7 +359,7 @@ public class EclipseAST extends AST { } private static class EcjReflectionCheck { - private static final String CUD_TYPE = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult"; public static Method addProblemToCompilationResult; public static final Throwable problem; @@ -367,7 +368,7 @@ public class EclipseAST extends AST { Throwable problem_ = null; Method m = null; try { - m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", Class.forName(CUD_TYPE), boolean.class, String.class, int.class, int.class); + m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class); } catch (Throwable t) { // That's problematic, but as long as no local classes are used we don't actually need it. // Better fail on local classes than crash altogether. diff --git a/src/core/lombok/eclipse/EclipseAstProblemView.java b/src/core/lombok/eclipse/EclipseAstProblemView.java index a2d5b833..c1179666 100644 --- a/src/core/lombok/eclipse/EclipseAstProblemView.java +++ b/src/core/lombok/eclipse/EclipseAstProblemView.java @@ -3,7 +3,6 @@ package lombok.eclipse; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.util.Util; @@ -13,14 +12,12 @@ public class EclipseAstProblemView { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; - char[] fileNameArray = ast.getFileName(); + if (result == null) return; if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray(); int lineNumber = 0; int columnNumber = 1; - CompilationResult result = ast.compilationResult; int[] lineEnds = null; lineNumber = sourceStart >= 0 ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1) @@ -33,7 +30,7 @@ public class EclipseAstProblemView { fileNameArray, message, 0, new String[0], isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error, sourceStart, sourceEnd, lineNumber, columnNumber); - ast.compilationResult.record(ecProblem, null); + result.record(ecProblem, null); } private static class LombokProblem extends DefaultProblem { diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 47e620f6..11caf5c2 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -144,7 +144,7 @@ public class TransformEclipseAST { try { String message = "Lombok can't parse this source: " + t.toString(); - EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0); + EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0); t.printStackTrace(); } catch (Throwable t2) { try { diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index d47b6715..7703336f 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -142,7 +142,7 @@ public class EclipseHandlerUtil { } catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends. new TerminalLogger().error(message, bundleName, error); } - if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0); + if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0); } /** diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index f9b53e68..8c6011bc 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -267,7 +267,7 @@ public class EclipsePatcher extends Agent { .build()); } - + private static void patchPostCompileHookEclipse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.IncrementalImageBuilder", "writeClassFileContents")) diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index cb3ee817..0002e26e 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -329,19 +329,16 @@ public class PatchFixes { return newSimpleNames; } - public static byte[] runPostCompiler(byte[] bytes, String fileName) { - if (System.getProperty("lombok.disablePostCompiler", null) != null) return bytes; + public static byte[] runPostCompiler(byte[] bytes, String fileName) { byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); return transformed == null ? bytes : transformed; } public static OutputStream runPostCompiler(OutputStream out) throws IOException { - if (System.getProperty("lombok.disablePostCompiler", null) != null) return out; return PostCompiler.wrapOutputStream(out, "TEST", DiagnosticsReceiver.CONSOLE); } public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { - if (System.getProperty("lombok.disablePostCompiler", null) != null) return out; String fileName = path + "/" + name; return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); } -- cgit From 5a3e9bd8049469169410107011ad0e26b3b629e3 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 31 May 2013 01:03:38 +0200 Subject: Added @NonNull on parameters feature (issue 514), including docs and changelog. --- buildScripts/website.ant.xml | 3 + doc/changelog.markdown | 1 + src/core/lombok/NonNull.java | 16 ++- .../eclipse/handlers/EclipseHandlerUtil.java | 7 +- .../lombok/eclipse/handlers/NonNullHandler.java | 147 ++++++++++++++++++++ .../lombok/javac/handlers/HandleSneakyThrows.java | 12 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 23 +++- src/core/lombok/javac/handlers/NonNullHandler.java | 148 +++++++++++++++++++++ src/utils/lombok/javac/Javac.java | 32 +++++ .../resource/after-delombok/DataOnLocalClass.java | 8 +- .../after-delombok/NonNullOnParameter.java | 48 +++++++ .../resource/after-delombok/NonNullPlain.java | 8 +- .../resource/after-delombok/SetterOnClass.java | 4 +- .../resource/after-delombok/WitherOnClass.java | 4 +- .../resource/after-ecj/DataOnLocalClass.java | 8 +- .../resource/after-ecj/NonNullOnParameter.java | 61 +++++++++ .../transform/resource/after-ecj/NonNullPlain.java | 8 +- .../resource/after-ecj/SetterOnClass.java | 4 +- .../resource/after-ecj/WitherOnClass.java | 4 +- .../resource/before/NonNullOnParameter.java | 30 +++++ .../NonNullOnParameter.java.messages | 1 + .../messages-delombok/NonNullPlain.java.messages | 1 + .../messages-ecj/NonNullOnParameter.java.messages | 3 + .../messages-ecj/NonNullPlain.java.messages | 1 + .../NonNullOnParameter.java.messages | 1 + .../messages-idempotent/NonNullPlain.java.messages | 3 + usage_examples/NonNullExample_post.jpage | 13 ++ usage_examples/NonNullExample_pre.jpage | 10 ++ website/features/Cleanup.html | 2 +- website/features/Data.html | 2 +- website/features/Delegate.html | 11 +- website/features/GetterLazy.html | 2 +- website/features/GetterSetter.html | 2 +- website/features/Log.html | 2 +- website/features/NonNull.html | 73 ++++++++++ website/features/SneakyThrows.html | 4 +- website/features/Synchronized.html | 2 +- website/features/ToString.html | 2 +- website/features/index.html | 18 +-- website/features/val.html | 2 +- 40 files changed, 674 insertions(+), 57 deletions(-) create mode 100644 src/core/lombok/eclipse/handlers/NonNullHandler.java create mode 100644 src/core/lombok/javac/handlers/NonNullHandler.java create mode 100644 test/transform/resource/after-delombok/NonNullOnParameter.java create mode 100644 test/transform/resource/after-ecj/NonNullOnParameter.java create mode 100644 test/transform/resource/before/NonNullOnParameter.java create mode 100644 test/transform/resource/messages-delombok/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-delombok/NonNullPlain.java.messages create mode 100644 test/transform/resource/messages-ecj/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-ecj/NonNullPlain.java.messages create mode 100644 test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-idempotent/NonNullPlain.java.messages create mode 100644 usage_examples/NonNullExample_post.jpage create mode 100644 usage_examples/NonNullExample_pre.jpage create mode 100644 website/features/NonNull.html (limited to 'doc') diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 405b388f..41130bd2 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -145,6 +145,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index e2e3d6b5..aaf66030 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* 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) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) diff --git a/src/core/lombok/NonNull.java b/src/core/lombok/NonNull.java index 5f5d8ed2..96813170 100644 --- a/src/core/lombok/NonNull.java +++ b/src/core/lombok/NonNull.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 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 @@ -28,12 +28,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Lombok is smart enough to translate any annotation named {@code @NonNull} in any casing and - * with any package name to the return type of generated getters and the parameter of generated setters and constructors, - * as well as generate the appropriate null checks in the setter and constructor. - * - * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just - * about anyone elses. As long as it is named {@code @NonNull}. + * If put on a parameter, lombok will insert a null-check at the start of the method / constructor's body, throwing a + * {@code NullPointerException} with the parameter's name as message. If put on a field, any generated method assigning + * a value to this field will also produce these nullchecks. + *

+ * Note that any annotation named {@code NonNull} with any casing and any package will result in nullchecks produced for + * generated methods (and the annotation will be copied to the getter return type and any parameters of generated methods), + * but only this annotation, if present on a parameter, will result in a null check inserted into your otherwise + * handwritten method. * * WARNING: If the java community ever does decide on supporting a single {@code @NonNull} annotation (for example via JSR305), then * this annotation will be deleted from the lombok package. If the need to update an import statement scares diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 7703336f..dc99dabf 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -61,6 +61,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CastExpression; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; @@ -1334,7 +1335,11 @@ public class EclipseHandlerUtil { EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL); equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE; setGeneratedBy(equalExpression, source); - IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0); + Block throwBlock = new Block(0); + throwBlock.statements = new Statement[] {throwStatement}; + throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE; + setGeneratedBy(throwBlock, source); + IfStatement ifStatement = new IfStatement(equalExpression, throwBlock, 0, 0); setGeneratedBy(ifStatement, source); return ifStatement; } diff --git a/src/core/lombok/eclipse/handlers/NonNullHandler.java b/src/core/lombok/eclipse/handlers/NonNullHandler.java new file mode 100644 index 00000000..5c58069c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/NonNullHandler.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import java.util.Arrays; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.mangosdk.spi.ProviderFor; + +import lombok.NonNull; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +@DeferUntilPostDiet +@ProviderFor(EclipseAnnotationHandler.class) +public class NonNullHandler extends EclipseAnnotationHandler { + @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + Argument arg; + AbstractMethodDeclaration declaration; + + try { + arg = (Argument) annotationNode.up().get(); + declaration = (AbstractMethodDeclaration) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + Statement nullCheck = generateNullCheck(arg, ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + if (declaration.statements == null) { + declaration.statements = new Statement[] {nullCheck}; + } else { + char[] expectedName = arg.name; + for (Statement stat : declaration.statements) { + char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (Arrays.equals(expectedName, varNameOfNullCheck)) return; + } + + Statement[] newStatements = new Statement[declaration.statements.length + 1]; + int skipOver = 0; + for (Statement stat : declaration.statements) { + if (isGenerated(stat)) skipOver++; + else break; + } + System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver); + System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver); + newStatements[skipOver] = nullCheck; + declaration.statements = newStatements; + } + annotationNode.up().up().rebuild(); + } + + private char[] returnVarNameIfNullCheck(Statement stat) { + if (!(stat instanceof IfStatement)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + Statement then = ((IfStatement) stat).thenStatement; + if (then instanceof Block) { + Statement[] blockStatements = ((Block) then).statements; + if (blockStatements == null || blockStatements.length == 0) return null; + then = blockStatements[0]; + } + + if (!(then instanceof ThrowStatement)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + Expression cond = ((IfStatement) stat).condition; + if (!(cond instanceof EqualExpression)) return null; + EqualExpression bin = (EqualExpression) cond; + int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT); + if (operatorId != OperatorIds.EQUAL_EQUAL) return null; + if (!(bin.left instanceof SingleNameReference)) return null; + if (!(bin.right instanceof NullLiteral)) return null; + return ((SingleNameReference) bin.left).token; + } + } +} diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java index c2394fc8..c818f630 100644 --- a/src/core/lombok/javac/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 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 @@ -36,8 +36,6 @@ import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; -import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -114,14 +112,6 @@ public class HandleSneakyThrows extends JavacAnnotationHandler { } } - private boolean isConstructorCall(final JCStatement supect) { - if (!(supect instanceof JCExpressionStatement)) return false; - final JCExpression supectExpression = ((JCExpressionStatement) supect).expr; - if (!(supectExpression instanceof JCMethodInvocation)) return false; - final String methodName = ((JCMethodInvocation) supectExpression).meth.toString(); - return "super".equals(methodName) || "this".equals(methodName); - } - private JCStatement buildTryCatchBlock(JavacNode node, List contents, String exception, JCTree source) { TreeMaker maker = node.getTreeMaker(); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index ef1a9f50..7cbaa5ac 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -50,9 +50,11 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; @@ -121,6 +123,7 @@ public class JavacHandlerUtil { } public static T recursiveSetGeneratedBy(T node, JCTree source) { + if (node == null) return null; setGeneratedBy(node, source); node.accept(new MarkingScanner(source)); @@ -543,6 +546,23 @@ public class JavacHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isConstructorCall(final JCStatement statement) { + if (!(statement instanceof JCExpressionStatement)) return false; + JCExpression expr = ((JCExpressionStatement) statement).expr; + if (!(expr instanceof JCMethodInvocation)) return false; + JCExpression invocation = ((JCMethodInvocation) expr).meth; + String name; + if (invocation instanceof JCFieldAccess) { + name = ((JCFieldAccess) invocation).name.toString(); + } else if (invocation instanceof JCIdent) { + name = ((JCIdent) invocation).name.toString(); + } else { + name = ""; + } + + return "super".equals(name) || "this".equals(name); + } + /** * Turns an {@code AccessLevel} instance into the flag bit used by javac. */ @@ -890,7 +910,8 @@ public class JavacHandlerUtil { JCExpression npe = chainDots(variable, "java", "lang", "NullPointerException"); JCTree exception = treeMaker.NewClass(null, List.nil(), npe, List.of(treeMaker.Literal(fieldName.toString())), null); JCStatement throwStatement = treeMaker.Throw(exception); - return treeMaker.If(treeMaker.Binary(CTC_EQUAL, treeMaker.Ident(fieldName), treeMaker.Literal(CTC_BOT, null)), throwStatement, null); + JCBlock throwBlock = treeMaker.Block(0, List.of(throwStatement)); + return treeMaker.If(treeMaker.Binary(CTC_EQUAL, treeMaker.Ident(fieldName), treeMaker.Literal(CTC_BOT, null)), throwBlock, null); } /** diff --git a/src/core/lombok/javac/handlers/NonNullHandler.java b/src/core/lombok/javac/handlers/NonNullHandler.java new file mode 100644 index 00000000..415d6032 --- /dev/null +++ b/src/core/lombok/javac/handlers/NonNullHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBinary; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCParens; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCThrow; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; + +import lombok.NonNull; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +@ProviderFor(JavacAnnotationHandler.class) +public class NonNullHandler extends JavacAnnotationHandler { + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((JCVariableDecl) annotationNode.up().get()).vartype)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + JCMethodDecl declaration; + + try { + declaration = (JCMethodDecl) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (JavacHandlerUtil.isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + JCStatement nullCheck = recursiveSetGeneratedBy(generateNullCheck(annotationNode.getTreeMaker(), annotationNode.up()), ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + List statements = declaration.body.stats; + + String expectedName = annotationNode.up().getName(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat)) continue; + String varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (varNameOfNullCheck.equals(expectedName)) return; + } + + List tail = statements; + List head = List.nil(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) { + tail = tail.tail; + head = head.prepend(stat); + continue; + } + break; + } + + List newList = tail.prepend(nullCheck); + for (JCStatement stat : head) newList = newList.prepend(stat); + declaration.body.stats = newList; + } + + /** + * Checks if the statement is of the form 'if (x == null) {throw WHATEVER;}, + * where the block braces are optional. If it is of this form, returns "x". + * If it is not of this form, returns null. + */ + private String returnVarNameIfNullCheck(JCStatement stat) { + if (!(stat instanceof JCIf)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + JCStatement then = ((JCIf) stat).thenpart; + if (then instanceof JCBlock) { + List stats = ((JCBlock) then).stats; + if (stats.length() == 0) return null; + then = stats.get(0); + } + if (!(then instanceof JCThrow)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + JCExpression cond = ((JCIf) stat).cond; + while (cond instanceof JCParens) cond = ((JCParens) cond).expr; + if (!(cond instanceof JCBinary)) return null; + JCBinary bin = (JCBinary) cond; + if (getTag(bin) != CTC_EQUAL) return null; + if (!(bin.lhs instanceof JCIdent)) return null; + if (!(bin.rhs instanceof JCLiteral)) return null; + if (((JCLiteral) bin.rhs).typetag != CTC_BOT) return null; + return ((JCIdent) bin.lhs).name.toString(); + } + } +} diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index b4e58b8f..08c7c957 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -21,6 +21,8 @@ */ package lombok.javac; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -129,4 +131,34 @@ public class Javac { throw new RuntimeException(e); } } + + private static final Field JCTREE_TAG; + private static final Method JCTREE_GETTAG; + static { + Field f = null; + try { + f = JCTree.class.getDeclaredField("tag"); + } catch (NoSuchFieldException e) {} + JCTREE_TAG = f; + + Method m = null; + try { + m = JCTree.class.getDeclaredMethod("getTag"); + } catch (NoSuchMethodException e) {} + JCTREE_GETTAG = m; + } + + public static int getTag(JCTree node) { + if (JCTREE_GETTAG != null) { + try { + return (Integer) JCTREE_GETTAG.invoke(node); + } catch (Exception e) {} + } + try { + return (Integer) JCTREE_TAG.get(node); + } catch (Exception e) { + throw new IllegalStateException("Can't get node tag"); + } + } + } diff --git a/test/transform/resource/after-delombok/DataOnLocalClass.java b/test/transform/resource/after-delombok/DataOnLocalClass.java index ed4d30ca..abe2757b 100644 --- a/test/transform/resource/after-delombok/DataOnLocalClass.java +++ b/test/transform/resource/after-delombok/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { String name; @java.lang.SuppressWarnings("all") public InnerLocal(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @lombok.NonNull @@ -73,7 +75,9 @@ class DataOnLocalClass2 { } @java.lang.SuppressWarnings("all") public void setName(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @java.lang.Override diff --git a/test/transform/resource/after-delombok/NonNullOnParameter.java b/test/transform/resource/after-delombok/NonNullOnParameter.java new file mode 100644 index 00000000..a27d19c9 --- /dev/null +++ b/test/transform/resource/after-delombok/NonNullOnParameter.java @@ -0,0 +1,48 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + if (arg == null) throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg3 == null) { + throw new java.lang.NullPointerException("arg3"); + } + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg != null) throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if (stringArg == null) { + throw new java.lang.NullPointerException("stringArg"); + } + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +} diff --git a/test/transform/resource/after-delombok/NonNullPlain.java b/test/transform/resource/after-delombok/NonNullPlain.java index 064e00b9..6b85cbf7 100644 --- a/test/transform/resource/after-delombok/NonNullPlain.java +++ b/test/transform/resource/after-delombok/NonNullPlain.java @@ -16,7 +16,9 @@ class NonNullPlain { @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") public NonNullPlain(@lombok.NonNull final int i, @lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -45,7 +47,9 @@ class NonNullPlain { @java.lang.SuppressWarnings("all") public void setS(@lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.s = s; } diff --git a/test/transform/resource/after-delombok/SetterOnClass.java b/test/transform/resource/after-delombok/SetterOnClass.java index 151bc179..7077c492 100644 --- a/test/transform/resource/after-delombok/SetterOnClass.java +++ b/test/transform/resource/after-delombok/SetterOnClass.java @@ -53,7 +53,9 @@ class SetterOnClass6 { } @java.lang.SuppressWarnings("all") public void setNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } } \ No newline at end of file diff --git a/test/transform/resource/after-delombok/WitherOnClass.java b/test/transform/resource/after-delombok/WitherOnClass.java index 783fede1..45d0c4b5 100644 --- a/test/transform/resource/after-delombok/WitherOnClass.java +++ b/test/transform/resource/after-delombok/WitherOnClass.java @@ -35,7 +35,9 @@ class WitherOnClass3 { } @java.lang.SuppressWarnings("all") public WitherOnClass3 withNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } return this.nonNull == nonNull ? this : new WitherOnClass3(this.couldBeNull, nonNull); } } diff --git a/test/transform/resource/after-ecj/DataOnLocalClass.java b/test/transform/resource/after-ecj/DataOnLocalClass.java index 137edf50..2f8dcca1 100644 --- a/test/transform/resource/after-ecj/DataOnLocalClass.java +++ b/test/transform/resource/after-ecj/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { } public @java.lang.SuppressWarnings("all") void setName(final @lombok.NonNull String name) { if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { @@ -96,7 +98,9 @@ class DataOnLocalClass2 { public @java.lang.SuppressWarnings("all") InnerLocal(final @lombok.NonNull String name) { super(); if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } } diff --git a/test/transform/resource/after-ecj/NonNullOnParameter.java b/test/transform/resource/after-ecj/NonNullOnParameter.java new file mode 100644 index 00000000..bbceb153 --- /dev/null +++ b/test/transform/resource/after-ecj/NonNullOnParameter.java @@ -0,0 +1,61 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + if ((arg == null)) + throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg3 == null)) + { + throw new java.lang.NullPointerException("arg3"); + } + if ((arg2 == null)) + { + throw new NullPointerException("arg2"); + } + if ((arg == null)) + System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg != null)) + throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if ((stringArg == null)) + { + throw new java.lang.NullPointerException("stringArg"); + } + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if ((arg == null)) + throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/NonNullPlain.java b/test/transform/resource/after-ecj/NonNullPlain.java index c9c96d0a..6e937f6a 100644 --- a/test/transform/resource/after-ecj/NonNullPlain.java +++ b/test/transform/resource/after-ecj/NonNullPlain.java @@ -8,7 +8,9 @@ import java.lang.annotation.*; public @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") NonNullPlain(final @lombok.NonNull int i, final @lombok.NonNull String s) { super(); if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -26,7 +28,9 @@ import java.lang.annotation.*; } public @java.lang.SuppressWarnings("all") void setS(final @lombok.NonNull String s) { if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.s = s; } public @java.lang.SuppressWarnings("all") void setO(final Object o) { diff --git a/test/transform/resource/after-ecj/SetterOnClass.java b/test/transform/resource/after-ecj/SetterOnClass.java index da928f24..aa3459bb 100644 --- a/test/transform/resource/after-ecj/SetterOnClass.java +++ b/test/transform/resource/after-ecj/SetterOnClass.java @@ -63,7 +63,9 @@ } public @java.lang.SuppressWarnings("all") void setNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } } diff --git a/test/transform/resource/after-ecj/WitherOnClass.java b/test/transform/resource/after-ecj/WitherOnClass.java index ff4566e5..82132e87 100644 --- a/test/transform/resource/after-ecj/WitherOnClass.java +++ b/test/transform/resource/after-ecj/WitherOnClass.java @@ -33,7 +33,9 @@ } public @java.lang.SuppressWarnings("all") WitherOnClass3 withNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } return ((this.nonNull == nonNull) ? this : new WitherOnClass3(this.couldBeNull, nonNull)); } } diff --git a/test/transform/resource/before/NonNullOnParameter.java b/test/transform/resource/before/NonNullOnParameter.java new file mode 100644 index 00000000..7eb4c565 --- /dev/null +++ b/test/transform/resource/before/NonNullOnParameter.java @@ -0,0 +1,30 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + } + + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg == null) throw new NullPointerException(); + } + + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + + public void test3(@lombok.NonNull String arg) { + if (arg != null) throw new IllegalStateException(); + } + + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + + } + + public void test(@lombok.NonNull String arg) { + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages new file mode 100644 index 00000000..ac87adcd --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +22:89 @NonNull is meaningless on a primitive. diff --git a/test/transform/resource/messages-delombok/NonNullPlain.java.messages b/test/transform/resource/messages-delombok/NonNullPlain.java.messages new file mode 100644 index 00000000..67eb8abe --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:9 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages new file mode 100644 index 00000000..1539929b --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages @@ -0,0 +1,3 @@ +15:460 Dead code +22:683 @NonNull is meaningless on a primitive. +28:823 Dead code \ No newline at end of file diff --git a/test/transform/resource/messages-ecj/NonNullPlain.java.messages b/test/transform/resource/messages-ecj/NonNullPlain.java.messages new file mode 100644 index 00000000..96eed252 --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:116 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages new file mode 100644 index 00000000..fd23a32a --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +33:89 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullPlain.java.messages b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages new file mode 100644 index 00000000..c48da311 --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages @@ -0,0 +1,3 @@ +4:9 @NonNull is meaningless on a primitive. +18:29 @NonNull is meaningless on a primitive. +44:26 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/usage_examples/NonNullExample_post.jpage b/usage_examples/NonNullExample_post.jpage new file mode 100644 index 00000000..24175e06 --- /dev/null +++ b/usage_examples/NonNullExample_post.jpage @@ -0,0 +1,13 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + if (person == null) { + throw new NullPointerException("person"); + } + this.name = person.getName(); + } +} diff --git a/usage_examples/NonNullExample_pre.jpage b/usage_examples/NonNullExample_pre.jpage new file mode 100644 index 00000000..47556ce7 --- /dev/null +++ b/usage_examples/NonNullExample_pre.jpage @@ -0,0 +1,10 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + this.name = person.getName(); + } +} diff --git a/website/features/Cleanup.html b/website/features/Cleanup.html index d1637dd4..a6e41f39 100644 --- a/website/features/Cleanup.html +++ b/website/features/Cleanup.html @@ -60,7 +60,7 @@

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

-

+

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

-

+

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

+

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

@NonNull

+ +
+

Overview

+

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

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

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

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

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

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

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

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

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

Small print

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

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

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

Lombok features

+
val
+
Finally! Hassle-free final local variables.
+
@NonNull
+
or: How I learned to stop worrying and love the NullPointerException.
+
@Cleanup
+
Automatic resource management: Call your close() methods safely with no hassle.
@Getter / @Setter
Never write public int getFoo() {return foo;} again.
-
@Getter(lazy=true)
-
Laziness is a virtue!
@ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!
@EqualsAndHashCode
@@ -26,16 +30,14 @@
@Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
-
@Cleanup
-
Automatic resource management: Call your close() methods safely with no hassle.
-
@Synchronized
-
synchronized done right: Don't expose your locks.
@SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
+
@Synchronized
+
synchronized done right: Don't expose your locks.
+
@Getter(lazy=true)
+
Laziness is a virtue!
@Log
Captain's Log, stardate 24435.7: "What was that line again?"
-
val
-
Finally! Hassle-free final local variables.
@Delegate
Don't lose your composition.
experimental features
diff --git a/website/features/val.html b/website/features/val.html index cec799e9..c4c8ad27 100644 --- a/website/features/val.html +++ b/website/features/val.html @@ -50,7 +50,7 @@
-- cgit From e1c39bbc601408decb0ae147d181708a5af41307 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 18 Jun 2013 04:23:15 +0200 Subject: javac builder implementation. Passes all tests. Added toString() impl for builders in both eclipse and javac. Added all documentation, though it'll need some reviewing. --- buildScripts/website.ant.xml | 3 + doc/changelog.markdown | 1 + .../lombok/eclipse/handlers/HandleBuilder.java | 7 +- .../lombok/eclipse/handlers/HandleToString.java | 8 +- src/core/lombok/javac/handlers/HandleBuilder.java | 133 +++++++++++++++++++-- src/core/lombok/javac/handlers/HandleSetter.java | 12 +- src/core/lombok/javac/handlers/HandleToString.java | 6 +- .../resource/after-delombok/BuilderComplex.java | 5 + .../resource/after-delombok/BuilderSimple.java | 5 + .../BuilderWithExistingBuilderClass.java | 5 + .../resource/after-ecj/BuilderComplex.java | 3 + .../resource/after-ecj/BuilderSimple.java | 3 + .../after-ecj/BuilderWithExistingBuilderClass.java | 3 + .../experimental/BuilderExample_post.jpage | 40 +++++++ .../experimental/BuilderExample_pre.jpage | 7 ++ website/features/experimental/Accessors.html | 2 +- website/features/experimental/Builder.html | 127 ++++++++++++++++++++ website/features/experimental/index.html | 2 + 18 files changed, 350 insertions(+), 22 deletions(-) create mode 100644 usage_examples/experimental/BuilderExample_post.jpage create mode 100644 usage_examples/experimental/BuilderExample_pre.jpage create mode 100644 website/features/experimental/Builder.html (limited to 'doc') diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 41130bd2..521ca59b 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -148,6 +148,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index aaf66030..85fcd86a 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d15f00e6..e2bf5fe2 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -78,7 +78,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (buildMethodName == null) builderMethodName = "build"; if (builderClassName == null) builderClassName = ""; - if (checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; if (!builderClassName.isEmpty()) { if (!checkName("builderClassName", builderClassName, annotationNode)) return; @@ -200,6 +200,11 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (md != null) injectMethod(builderType, md); } + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) injectMethod(builderType, md); + } + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index d864153f..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler { } } - private MethodDeclaration createToString(EclipseNode type, Collection fields, + static MethodDeclaration createToString(EclipseNode type, Collection fields, boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); @@ -282,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler { return method; } - private String getTypeName(EclipseNode type) { + private static String getTypeName(EclipseNode type) { String typeName = getSingleTypeName(type); EclipseNode upType = type.up(); while (upType.getKind() == Kind.TYPE) { @@ -292,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler { return typeName; } - private String getSingleTypeName(EclipseNode type) { + private static String getSingleTypeName(EclipseNode type) { TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); char[] rawTypeName = typeDeclaration.name; return rawTypeName == null ? "" : new String(rawTypeName); @@ -301,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler { private static final Set BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(Arrays.asList( "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; NameReference ref; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index c39255f2..aa485b26 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -22,21 +22,28 @@ package lombok.javac.handlers; import java.util.ArrayList; +import java.util.Collections; + +import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; import lombok.AccessLevel; @@ -51,6 +58,7 @@ import static lombok.javac.Javac.*; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; +@ProviderFor(JavacAnnotationHandler.class) public class HandleBuilder extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { Builder builderInstance = annotation.getInstance(); @@ -68,6 +76,9 @@ public class HandleBuilder extends JavacAnnotationHandler { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } + deleteAnnotationIfNeccessary(annotationNode, Builder.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + JavacNode parent = annotationNode.up(); java.util.List typesOfParameters = new ArrayList(); @@ -93,7 +104,7 @@ public class HandleBuilder extends JavacAnnotationHandler { returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; - thrownExceptions = null; + thrownExceptions = List.nil(); nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("")) { @@ -107,7 +118,7 @@ public class HandleBuilder extends JavacAnnotationHandler { typeParams = td.typarams; thrownExceptions = fillParametersFrom.thrown; nameOfStaticBuilderMethod = null; - if (builderClassName.isEmpty()) builderClassName = td.name.toString(); + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null) { tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); @@ -124,21 +135,27 @@ public class HandleBuilder extends JavacAnnotationHandler { returnType = ((JCTypeApply) returnType).clazz; } if (returnType instanceof JCFieldAccess) { - builderClassName = ((JCFieldAccess) returnType).name.toString(); + builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder"; } else if (returnType instanceof JCIdent) { Name n = ((JCIdent) returnType).name; for (JCTypeParameter tp : typeParams) { - if (tp.name.contentEquals(n)) { + if (tp.name.equals(n)) { annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); return; } } - builderClassName = n.toString(); + builderClassName = n.toString() + "Builder"; + } else if (returnType instanceof JCPrimitiveTypeTree) { + builderClassName = returnType.toString() + "Builder"; + if (Character.isLowerCase(builderClassName.charAt(0))) { + builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); + } + } else { // This shouldn't happen. System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); - builderClassName = td.name.toString(); + builderClassName = td.name.toString() + "Builder"; } } } else { @@ -158,7 +175,7 @@ public class HandleBuilder extends JavacAnnotationHandler { java.util.List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); java.util.List newMethods = new ArrayList(); for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuider(builderType, fieldNode, ast); + JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast); if (newMethod != null) newMethods.add(newMethod); } @@ -170,16 +187,112 @@ public class HandleBuilder extends JavacAnnotationHandler { for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); if (md != null) injectMethod(tdParent, md); } } + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List fieldNames, JavacNode type, List thrownExceptions) { + TreeMaker maker = type.getTreeMaker(); + + JCExpression call; + JCStatement statement; + + ListBuffer args = ListBuffer.lb(); + for (Name n : fieldNames) { + args.append(maker.Ident(n)); + } + + if (staticName == null) { + call = maker.NewClass(null, List.nil(), returnType, args.toList(), null); + statement = maker.Return(call); + } else { + ListBuffer typeParams = ListBuffer.lb(); + for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { + typeParams.append(maker.Ident(tp.name)); + } + + JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); + call = maker.Apply(typeParams.toList(), fn, args.toList()); + if (returnType instanceof JCPrimitiveTypeTree && ((JCPrimitiveTypeTree) returnType).typetag == CTC_VOID) { + statement = maker.Exec(call); + } else { + statement = maker.Return(call); + } + } + + JCBlock body = maker.Block(0, List.of(statement)); + + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.nil(), List.nil(), thrownExceptions, body, null); + } + + private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List typeParams) { + TreeMaker maker = type.getTreeMaker(); + + ListBuffer typeArgs = ListBuffer.lb(); + for (JCTypeParameter typeParam : typeParams) { + typeArgs.append(maker.Ident(typeParam.name)); + } + + JCExpression call = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.nil(), null); + JCStatement statement = maker.Return(call); + + JCBlock body = maker.Block(0, List.of(statement)); + return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.nil(), List.nil(), body, null); + } + + private java.util.List addFieldsToBuilder(JavacNode builderType, java.util.List namesOfParameters, java.util.List typesOfParameters, JCTree source) { + int len = namesOfParameters.size(); + java.util.List existing = new ArrayList(); + for (JavacNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + java.util.Listout = new ArrayList(); + + top: + for (int i = len - 1; i >= 0; i--) { + Name name = namesOfParameters.get(i); + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(name)) { + out.add(exists); + continue top; + } + } + TreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null); + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + return out; + } + + + private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) { + Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + for (JavacNode child : builderType.down()) { + if (child.getKind() != Kind.METHOD) continue; + Name existingName = ((JCMethodDecl) child.get()).name; + if (existingName.equals(fieldName)) return null; + } + + TreeMaker maker = builderType.getTreeMaker(); + return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.nil(), List.nil()); + } + private JavacNode findInnerClass(JavacNode parent, String name) { for (JavacNode child : parent.down()) { if (child.getKind() != Kind.TYPE) continue; @@ -192,7 +305,7 @@ public class HandleBuilder extends JavacAnnotationHandler { private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List typeParams, JCAnnotation ast) { TreeMaker maker = tdParent.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); - JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), typeParams, null, List.nil(), List.nil()); + JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.nil(), List.nil()); return injectType(tdParent, builder); } } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index c1e03c35..29728eae 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -194,9 +194,13 @@ public class HandleSetter extends JavacAnnotationHandler { injectMethod(fieldNode.up(), createdSetter); } - private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List onMethod, List onParam) { + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List onMethod, List onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); + return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + } + + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List onMethod, List onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -222,17 +226,17 @@ public class HandleSetter extends JavacAnnotationHandler { } JCExpression methodType = null; - if (returnThis) { + if (shouldReturnThis) { methodType = cloneSelfType(field); } if (methodType == null) { //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. methodType = treeMaker.Type(new JCNoType(CTC_VOID)); - returnThis = false; + shouldReturnThis = false; } - if (returnThis) { + if (shouldReturnThis) { JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); statements.append(returnStatement); } diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 5b3c033c..333393da 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -24,6 +24,8 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; +import java.util.Collection; + import lombok.ToString; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; @@ -164,7 +166,7 @@ public class HandleToString extends JavacAnnotationHandler { } } - private JCMethodDecl createToString(JavacNode typeNode, List fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + static JCMethodDecl createToString(JavacNode typeNode, Collection fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.nil()); @@ -241,7 +243,7 @@ public class HandleToString extends JavacAnnotationHandler { List.nil(), List.nil(), List.nil(), body, null), source); } - private String getTypeName(JavacNode typeNode) { + private static String getTypeName(JavacNode typeNode) { String typeName = ((JCClassDecl) typeNode.get()).name.toString(); JavacNode upType = typeNode.up(); while (upType.getKind() == Kind.TYPE) { diff --git a/test/transform/resource/after-delombok/BuilderComplex.java b/test/transform/resource/after-delombok/BuilderComplex.java index d6d12ebf..3c97f92a 100644 --- a/test/transform/resource/after-delombok/BuilderComplex.java +++ b/test/transform/resource/after-delombok/BuilderComplex.java @@ -35,6 +35,11 @@ class BuilderComplex { public void execute() { BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderComplex.VoidBuilder(number=" + this.number + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ", selfRef=" + this.selfRef + ")"; + } } @java.lang.SuppressWarnings("all") public static VoidBuilder builder() { diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java index 113d538e..24ac369c 100644 --- a/test/transform/resource/after-delombok/BuilderSimple.java +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -30,6 +30,11 @@ class BuilderSimple { public BuilderSimple build() { return new BuilderSimple(yes, also); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes + ", also=" + this.also + ")"; + } } @java.lang.SuppressWarnings("all") public static BuilderSimpleBuilder builder() { diff --git a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java index a8800009..1d40dbfa 100644 --- a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java @@ -25,6 +25,11 @@ class BuilderWithExistingBuilderClass { public BuilderWithExistingBuilderClass build() { return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1 + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ")"; + } } @java.lang.SuppressWarnings("all") public static BuilderWithExistingBuilderClassBuilder builder() { diff --git a/test/transform/resource/after-ecj/BuilderComplex.java b/test/transform/resource/after-ecj/BuilderComplex.java index ca302a3d..19aeb043 100644 --- a/test/transform/resource/after-ecj/BuilderComplex.java +++ b/test/transform/resource/after-ecj/BuilderComplex.java @@ -28,6 +28,9 @@ class BuilderComplex { public @java.lang.SuppressWarnings("all") void execute() { BuilderComplex.testVoidWithGenerics(number, arg2, arg3, selfRef); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((("BuilderComplex.VoidBuilder(number=" + this.number) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ", selfRef=") + this.selfRef) + ")"); + } } BuilderComplex() { super(); diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java index 4ca8e120..228b1928 100644 --- a/test/transform/resource/after-ecj/BuilderSimple.java +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -17,6 +17,9 @@ import java.util.List; public @java.lang.SuppressWarnings("all") BuilderSimple build() { return new BuilderSimple(yes, also); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes) + ", also=") + this.also) + ")"); + } } private final int noshow = 0; private final int yes; diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java index 02ed259e..38cb0038 100644 --- a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -20,6 +20,9 @@ class BuilderWithExistingBuilderClass { public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClass build() { return BuilderWithExistingBuilderClass.staticMethod(arg1, arg2, arg3); } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ")"); + } } BuilderWithExistingBuilderClass() { super(); diff --git a/usage_examples/experimental/BuilderExample_post.jpage b/usage_examples/experimental/BuilderExample_post.jpage new file mode 100644 index 00000000..624b236b --- /dev/null +++ b/usage_examples/experimental/BuilderExample_post.jpage @@ -0,0 +1,40 @@ +public class BuilderExample { + private String name; + private int age; + + BuilderExample(String name, int age) { + this.name = name; + this.age = age; + } + + public static BuilderExampleBuilder builder() { + return new BuilderExampleBuilder(); + } + + public static class BuilderExampleBuilder { + private String name; + private int age; + + BuilderExampleBuilder() { + } + + public BuilderExampleBuilder name(String name) { + this.name = name; + return this; + } + + public BuilderExampleBuilder age(int age) { + this.age = age; + return this; + } + + public BuilderExample build() { + return new BuilderExample(name, age); + } + + @java.lang.Override + public String toString() { + return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ")"; + } + } +} \ No newline at end of file diff --git a/usage_examples/experimental/BuilderExample_pre.jpage b/usage_examples/experimental/BuilderExample_pre.jpage new file mode 100644 index 00000000..9c754352 --- /dev/null +++ b/usage_examples/experimental/BuilderExample_pre.jpage @@ -0,0 +1,7 @@ +import lombok.experimental.Builder; + +@Builder +public class BuilderExample { + private String name; + private int age; +} diff --git a/website/features/experimental/Accessors.html b/website/features/experimental/Accessors.html index dce77d32..3ca79de5 100644 --- a/website/features/experimental/Accessors.html +++ b/website/features/experimental/Accessors.html @@ -84,7 +84,7 @@
diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html new file mode 100644 index 00000000..5ba74a27 --- /dev/null +++ b/website/features/experimental/Builder.html @@ -0,0 +1,127 @@ + + + + + + + + EXPERIMENTAL - @Builder +
+
+
+ +

@Builder

+ +
+

Since

+

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

+
+
+

Experimental

+

+ Experimental because: +

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

Overview

+

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

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

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

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

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

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

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

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

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

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

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

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

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

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

+ Generics are sorted out for you. +

+
+
+ +
+
+
+ + + diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 24fbb541..d0a086a0 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -22,6 +22,8 @@ Features that receive positive community feedback and which seem to produce clean, flexible code will eventually become accepted as a core feature and move out of the experimental package.
+
@Builder
+
It's like drinking tea with an extended pinky while wearing a monocle: No-hassle fancy-pants APIs for object creation!
@Accessors
A more fluent API for getters and setters.
@ExtensionMethod
-- cgit From b5747963c022f680168ff66ebdc7860adb954882 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:23:02 +0200 Subject: Value has been promoted to the main package. --- doc/changelog.markdown | 2 +- src/core/lombok/Data.java | 4 +- src/core/lombok/Value.java | 58 ++++++++++++++++ src/core/lombok/core/LombokInternalAliasing.java | 52 ++++++++++++++ src/core/lombok/core/TypeLibrary.java | 6 ++ src/core/lombok/core/TypeResolver.java | 9 +-- src/core/lombok/eclipse/EclipseImportList.java | 13 ++-- src/core/lombok/eclipse/handlers/HandleValue.java | 2 +- src/core/lombok/javac/JavacImportList.java | 9 ++- src/core/lombok/javac/handlers/HandleValue.java | 4 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 19 ++++- src/delombok/lombok/delombok/DelombokApp.java | 1 + .../resource/after-delombok/ValueExperimental.java | 46 ++++++++++++ .../ValueExperimentalStarImport.java | 25 +++++++ .../resource/after-ecj/ValueExperimental.java | 39 +++++++++++ .../after-ecj/ValueExperimentalStarImport.java | 20 ++++++ test/transform/resource/after-ecj/ValuePlain.java | 4 +- .../resource/before/ValueExperimental.java | 9 +++ .../before/ValueExperimentalStarImport.java | 5 ++ test/transform/resource/before/ValuePlain.java | 4 +- website/features/Data.html | 2 +- website/features/SneakyThrows.html | 2 +- website/features/Value.html | 76 ++++++++++++++++++++ website/features/experimental/Value.html | 81 ---------------------- website/features/experimental/Wither.html | 2 +- website/features/experimental/index.html | 9 ++- website/features/experimental/onX.html | 2 +- website/features/index.html | 2 + 28 files changed, 396 insertions(+), 111 deletions(-) create mode 100644 src/core/lombok/Value.java create mode 100644 src/core/lombok/core/LombokInternalAliasing.java create mode 100644 test/transform/resource/after-delombok/ValueExperimental.java create mode 100644 test/transform/resource/after-delombok/ValueExperimentalStarImport.java create mode 100644 test/transform/resource/after-ecj/ValueExperimental.java create mode 100644 test/transform/resource/after-ecj/ValueExperimentalStarImport.java create mode 100644 test/transform/resource/before/ValueExperimental.java create mode 100644 test/transform/resource/before/ValueExperimentalStarImport.java create mode 100644 website/features/Value.html delete mode 100644 website/features/experimental/Value.html (limited to 'doc') 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 fb32de51afaef2e2a3135b7230f47add005bae69 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 25 Jun 2013 00:29:57 +0200 Subject: And now the changelogs are also updated to reflect that Value has been promoted. Racking up the commits here... --- doc/changelog.markdown | 1 + 1 file changed, 1 insertion(+) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 95ee5764..e922f8c9 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) -- cgit From 91bfd390e05ed3d04dff6438ffd1bc9e01eb1fff Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 21:49:23 +0200 Subject: updated docs for new desugaring for getter(lazy=true) --- doc/changelog.markdown | 1 + usage_examples/GetterLazyExample_post.jpage | 19 ++++++++++--------- website/features/GetterLazy.html | 10 ++-------- 3 files changed, 13 insertions(+), 17 deletions(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index e922f8c9..c9dafc61 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). * FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) diff --git a/usage_examples/GetterLazyExample_post.jpage b/usage_examples/GetterLazyExample_post.jpage index 9f4b1ba3..afed1748 100644 --- a/usage_examples/GetterLazyExample_post.jpage +++ b/usage_examples/GetterLazyExample_post.jpage @@ -1,18 +1,19 @@ public class GetterLazyExample { - private double[] $lombok$lazy1v; - private volatile boolean $lombok$lazy1i; - private final Object $lombok$lazyLock = new Object[0]; + private final java.util.concurrent.AtomicReference cached = new java.util.concurrent.AtomicReference(); public double[] getCached() { - if (!this.$lombok$lazy1i) { - synchronized (this.$lombok$lazyLock) { - if (!this.$lombok$lazy1i) { - this.$lombok$lazy1v = expensive(); - this.$lombok$lazy1i = true; + java.lang.Object value = this.cached.get(); + if (value == null) { + synchronized(value) { + value = this.cached.get(); + if (value == null) { + final double[] actualValue = expensive(); + value = actualValue == null ? this.cached : actualValue; + this.cached.set(value); } } } - return this.$lombok$lazy1v; + return (double[])(value == this.cached ? null : value); } private double[] expensive() { diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index f70c8e78..c6d21f01 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -15,12 +15,7 @@

Overview

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

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

Small print

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

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

-- cgit From 0d5fea94da2bfb72ea886a7379ad35e124489692 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 05:50:08 +0200 Subject: Support for javadoc copying in Getter/Setter generation for javac, as well as updates to all relevant documentation --- doc/changelog.markdown | 1 + src/core/lombok/javac/handlers/HandleGetter.java | 1 + src/core/lombok/javac/handlers/HandleSetter.java | 4 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 91 ++++++++++++++++++++++ usage_examples/GetterSetterExample_post.jpage | 22 ++++++ usage_examples/GetterSetterExample_pre.jpage | 14 ++++ website/features/GetterSetter.html | 2 + 7 files changed, 134 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/changelog.markdown b/doc/changelog.markdown index c9dafc61..75825d77 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* FEATURE: javadoc on fields will now be copied to generated getters / setters. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). * CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 413404c0..51642f86 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -253,6 +253,7 @@ public class HandleGetter extends JavacAnnotationHandler { if (toClearOfMarkers != null) recursiveSetGeneratedBy(toClearOfMarkers, null); decl.mods.annotations = decl.mods.annotations.appendList(delegates); + copyJavadoc(field, decl, CopyJavadoc.GETTER); return decl; } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 29728eae..282e6c2f 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -252,8 +252,10 @@ public class HandleSetter extends JavacAnnotationHandler { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.SETTER); + return decl; } private static class JCNoType extends Type implements NoType { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 1784be90..87493e39 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; +import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.AccessLevel; @@ -1163,4 +1164,94 @@ public class JavacHandlerUtil { // This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline. return (JCExpression) in; } + + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*(GETTER|SETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + + private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { + Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(javadoc); + return m.replaceAll(""); + } + + private static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) { + Matcher m = SECTION_FINDER.matcher(javadoc); + int getterSectionHeaderStart = -1; + int getterSectionStart = -1; + int getterSectionEnd = -1; + while (m.find()) { + if (m.group(1).equalsIgnoreCase(sectionName)) { + getterSectionStart = m.end() + 1; + getterSectionHeaderStart = m.start(); + } else if (getterSectionStart != -1) { + getterSectionEnd = m.start(); + } + } + + if (getterSectionStart != -1) { + if (getterSectionEnd != -1) { + return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)}; + } else { + return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)}; + } + } + + return null; + } + + public static enum CopyJavadoc { + VERBATIM, GETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @return from the original and @param from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*"); + return new String[] {copy, javadoc}; + } + }, + SETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "SETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + }; + + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ + public String[] split(String javadoc) { + return new String[] {javadoc, javadoc}; + } + } + + /** + * Copies javadoc on one node to the other. + * + * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is + * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines. any {@code @return} lines + * are stripped from 'from'. + * + * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped. + */ + public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) { + if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; + try { + JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); + if (cu.docComments != null) { + String javadoc = cu.docComments.get(from.get()); + + if (javadoc != null) { + String[] filtered = copyMode.split(javadoc); + cu.docComments.put(to, filtered[0]); + cu.docComments.put(from.get(), filtered[1]); + } + } + } catch (Exception ignore) {} + } } diff --git a/usage_examples/GetterSetterExample_post.jpage b/usage_examples/GetterSetterExample_post.jpage index b99ba7e1..241a3a4e 100644 --- a/usage_examples/GetterSetterExample_post.jpage +++ b/usage_examples/GetterSetterExample_post.jpage @@ -1,19 +1,41 @@ public class GetterSetterExample { + /** + * Age of the person. Water is wet. + */ private int age = 10; + + /** + * Name of the person. + */ private String name; @Override public String toString() { return String.format("%s (age: %d)", name, age); } + /** + * Age of the person. Water is wet. + * + * @return The current value of this person's age. Circles are round. + */ public int getAge() { return age; } + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + */ public void setAge(int age) { this.age = age; } + /** + * Changes the name of this person. + * + * @param name The new value. + */ protected void setName(String name) { this.name = name; } diff --git a/usage_examples/GetterSetterExample_pre.jpage b/usage_examples/GetterSetterExample_pre.jpage index 9ef0532c..4183aa5d 100644 --- a/usage_examples/GetterSetterExample_pre.jpage +++ b/usage_examples/GetterSetterExample_pre.jpage @@ -3,7 +3,21 @@ import lombok.Getter; import lombok.Setter; public class GetterSetterExample { + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + * @return The current value of this person's age. Circles are round. + */ @Getter @Setter private int age = 10; + + /** + * Name of the person. + * -- SETTER -- + * Changes the name of this person. + * + * @param name The new value. + */ @Setter(AccessLevel.PROTECTED) private String name; @Override public String toString() { diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index c78b03bd..7e2ff226 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -30,6 +30,8 @@ behaviour of a @Getter, @Setter or @Data annotation on a class.

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

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

-- cgit From 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 'doc') diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 521ca59b..baf869a4 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -142,6 +142,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + @@ -163,9 +166,6 @@ such as converting the changelog into HTML, and creating javadoc. - - - diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 75825d77..d15e24be 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,7 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) -* FEATURE: javadoc on fields will now be copied to generated getters / setters. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). +* FEATURE: javadoc on fields will now be copied to generated getters / setters / withers. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). [@Wither documentation](http://projectlombok.org/features/experimental/Wither.html). * CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). * PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). * FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index ba5aa72d..b3f218b8 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -33,6 +33,7 @@ import lombok.core.TransformationsUtil; import lombok.experimental.Wither; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -264,7 +265,9 @@ public class HandleWither extends JavacAnnotationHandler { if (isFieldDeprecated(field)) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.WITHER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 6aed5508..a24dad7d 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1165,7 +1165,7 @@ public class JavacHandlerUtil { return (JCExpression) in; } - private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); @@ -1213,17 +1213,26 @@ public class JavacHandlerUtil { }, SETTER { @Override public String[] split(String javadoc) { - // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. - String[] out = splitJavadocOnSectionIfPresent(javadoc, "SETTER"); - if (out != null) return out; - // failing that, create a copy, but strip @param from the original and @return from the copy. - String copy = javadoc; - javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); - copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); - return new String[] {copy, javadoc}; + return splitForSetters(javadoc, "SETTER"); + } + }, + WITHER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "WITHER"); } }; + private static String[] splitForSetters(String javadoc, String sectionName) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ public String[] split(String javadoc) { return new String[] {javadoc, javadoc}; diff --git a/test/transform/resource/after-delombok/WitherDeprecated.java b/test/transform/resource/after-delombok/WitherDeprecated.java index f076d90e..29192012 100644 --- a/test/transform/resource/after-delombok/WitherDeprecated.java +++ b/test/transform/resource/after-delombok/WitherDeprecated.java @@ -12,6 +12,9 @@ class WitherDeprecated { public WitherDeprecated withAnnotation(final int annotation) { return this.annotation == annotation ? this : new WitherDeprecated(annotation, this.javadoc); } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public WitherDeprecated withJavadoc(final int javadoc) { diff --git a/usage_examples/ValueExample_post.jpage b/usage_examples/ValueExample_post.jpage new file mode 100644 index 00000000..ac9b64d1 --- /dev/null +++ b/usage_examples/ValueExample_post.jpage @@ -0,0 +1,120 @@ +import java.util.Arrays; + +public final class ValueExample { + private final String name; + private int age; + private final double score; + protected final String[] tags; + + @java.beans.ConstructorProperties({"name", "age", "score", "tags"}) + public ValueExample(String name, int age, double score, String[] tags) { + this.name = name; + this.age = age; + this.score = score; + this.tags = tags; + } + + public String getName() { + return this.name; + } + + public int getAge() { + return this.age; + } + + public double getScore() { + return this.score; + } + + public String[] getTags() { + return this.tags; + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample)) return false; + final ValueExample other = (ValueExample)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + if (this.getAge() != other.getAge()) return false; + if (Double.compare(this.getScore(), other.getScore()) != 0) return false; + if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + result = result * PRIME + this.getAge(); + final long $score = Double.doubleToLongBits(this.getScore()); + result = result * PRIME + (int)($score >>> 32 ^ $score); + result = result * PRIME + Arrays.deepHashCode(this.getTags()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")"; + } + + ValueExample withAge(int age) { + return this.age == age ? this : new ValueExample(name, age, score, tags); + } + + public static final class Exercise { + private final String name; + private final T value; + + private Exercise(String name, T value) { + this.name = name; + this.value = value; + } + + public static Exercise of(String name, T value) { + return new Exercise(name, value); + } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample.Exercise)) return false; + final Exercise other = (Exercise)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + final Object this$value = this.getValue(); + final Object other$value = other.getValue(); + if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + final Object $value = this.getValue(); + result = result * PRIME + ($value == null ? 0 : $value.hashCode()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; + } + } +} \ No newline at end of file diff --git a/usage_examples/ValueExample_pre.jpage b/usage_examples/ValueExample_pre.jpage new file mode 100644 index 00000000..d9550c25 --- /dev/null +++ b/usage_examples/ValueExample_pre.jpage @@ -0,0 +1,19 @@ +import lombok.AccessLevel; +import lombok.experimental.NonFinal; +import lombok.experimental.Value; +import lombok.experimental.Wither; +import lombok.ToString; + +@Value public class ValueExample { + String name; + @Wither(AccessLevel.PACKAGE) @NonFinal int age; + double score; + protected String[] tags; + + @ToString(includeFieldNames=true) + @Value(staticConstructor="of") + public static class Exercise { + String name; + T value; + } +} diff --git a/usage_examples/experimental/ValueExample_post.jpage b/usage_examples/experimental/ValueExample_post.jpage deleted file mode 100644 index ac9b64d1..00000000 --- a/usage_examples/experimental/ValueExample_post.jpage +++ /dev/null @@ -1,120 +0,0 @@ -import java.util.Arrays; - -public final class ValueExample { - private final String name; - private int age; - private final double score; - protected final String[] tags; - - @java.beans.ConstructorProperties({"name", "age", "score", "tags"}) - public ValueExample(String name, int age, double score, String[] tags) { - this.name = name; - this.age = age; - this.score = score; - this.tags = tags; - } - - public String getName() { - return this.name; - } - - public int getAge() { - return this.age; - } - - public double getScore() { - return this.score; - } - - public String[] getTags() { - return this.tags; - } - - @java.lang.Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof ValueExample)) return false; - final ValueExample other = (ValueExample)o; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; - if (this.getAge() != other.getAge()) return false; - if (Double.compare(this.getScore(), other.getScore()) != 0) return false; - if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 0 : $name.hashCode()); - result = result * PRIME + this.getAge(); - final long $score = Double.doubleToLongBits(this.getScore()); - result = result * PRIME + (int)($score >>> 32 ^ $score); - result = result * PRIME + Arrays.deepHashCode(this.getTags()); - return result; - } - - @java.lang.Override - public String toString() { - return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")"; - } - - ValueExample withAge(int age) { - return this.age == age ? this : new ValueExample(name, age, score, tags); - } - - public static final class Exercise { - private final String name; - private final T value; - - private Exercise(String name, T value) { - this.name = name; - this.value = value; - } - - public static Exercise of(String name, T value) { - return new Exercise(name, value); - } - - public String getName() { - return this.name; - } - - public T getValue() { - return this.value; - } - - @java.lang.Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof ValueExample.Exercise)) return false; - final Exercise other = (Exercise)o; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; - final Object this$value = this.getValue(); - final Object other$value = other.getValue(); - if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 0 : $name.hashCode()); - final Object $value = this.getValue(); - result = result * PRIME + ($value == null ? 0 : $value.hashCode()); - return result; - } - - @java.lang.Override - public String toString() { - return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; - } - } -} \ No newline at end of file diff --git a/usage_examples/experimental/ValueExample_pre.jpage b/usage_examples/experimental/ValueExample_pre.jpage deleted file mode 100644 index d9550c25..00000000 --- a/usage_examples/experimental/ValueExample_pre.jpage +++ /dev/null @@ -1,19 +0,0 @@ -import lombok.AccessLevel; -import lombok.experimental.NonFinal; -import lombok.experimental.Value; -import lombok.experimental.Wither; -import lombok.ToString; - -@Value public class ValueExample { - String name; - @Wither(AccessLevel.PACKAGE) @NonFinal int age; - double score; - protected String[] tags; - - @ToString(includeFieldNames=true) - @Value(staticConstructor="of") - public static class Exercise { - String name; - T value; - } -} diff --git a/website/features/experimental/Wither.html b/website/features/experimental/Wither.html index 9cbcd5ed..b334cd7c 100644 --- a/website/features/experimental/Wither.html +++ b/website/features/experimental/Wither.html @@ -46,6 +46,8 @@ a 'wither' is generated for each field (even non-final fields).

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

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

-- cgit From 1037e8322560be0721d70c2c5bfeca9d42157d85 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 22:13:00 +0200 Subject: possible fix for a deadlock issue 531. --- .../ivy-repo/org.projectlombok-lombok.patcher-0.7.xml | 14 ++++++++++++++ buildScripts/ivy.xml | 2 +- doc/changelog.markdown | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml (limited to 'doc') diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml new file mode 100644 index 00000000..ca41f64c --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index fd6c503a..7afc8a64 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -12,7 +12,7 @@ - + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index d15e24be..95c31db2 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -10,6 +10,7 @@ Lombok Changelog * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) +* BUGFIX: Possibly fixed a race condition in patcher [Issue #531](https://code.google.com/p/projectlombok/issues/detail?id=531). ### v0.11.8 (April 23rd, 2013) * FEATURE: Major performance improvements in eclipse by profiling the project clean process. -- cgit From 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 'doc') 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
Maven or Ivy
Maven or Ivy Lombok is in maven central. More…
Javac
NetBeans Just put lombok.jar on the classpath and enable annotation processing. More…
Eclipse and STSRun lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project.
Eclipse and variantsRun lombok.jar as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project. Supported variants: Springsource Tool Suite, JBoss Developer Studio
IDEA IntelliJ
IDEA IntelliJ lombok doesn't (yet) work under IntelliJ. We're working on it though!
Javadoc
GWT Lombok works with GWT. More…
Play! Framework
Play! Framework Use Aaron Freeman's lombok play plugin.
ecj