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.
---
.../EqualsAndHashCodeWithSomeExistingMethods.java.messages | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages
(limited to 'test/transform/resource/messages-delombok')
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).
--
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 'test/transform/resource/messages-delombok')
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.
-
or: How I learned to stop worrying and love the NullPointerException.
+
+
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.
+
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 @@
All together now: A shortcut for @ToString, @EqualsAndHashCode,
@Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
--
cgit
From 7af9add9996f2efab6cccc50c5503b3457534930 Mon Sep 17 00:00:00 2001
From: Reinier Zwitserloot
Date: Tue, 16 Jul 2013 00:51:31 +0200
Subject: * Fixed issues with @FieldDefaults and @Value (you can NOT override
@Value's final-by-default and private-by-default with it; now appropriate
warnings are emitted) * Builder now errors out on presence of most lombok
annotations on an explicit builder class. * Builder now takes
@FieldDefaults/@Value into account. * Builder on type now generates the
constructor as package private instead of private to avoid synthetic accessor
constructors. * added a bunch of test cases. * added a test case feature: If
the expected file is omitted entirely but there are expected messages, the
differences in the output itself are ignored. * streamlined checking for
boolean-ness (removed some duplicate code) * added 'fluent' and 'chain' to
@Builder.
---
src/core/lombok/core/TransformationsUtil.java | 25 ++++++++++++++--
.../eclipse/handlers/EclipseHandlerUtil.java | 35 ++++++++++++++++++++--
.../lombok/eclipse/handlers/HandleBuilder.java | 30 +++++++++++++++----
.../eclipse/handlers/HandleFieldDefaults.java | 10 ++++++-
src/core/lombok/eclipse/handlers/HandleGetter.java | 2 +-
src/core/lombok/eclipse/handlers/HandleSetter.java | 2 +-
src/core/lombok/eclipse/handlers/HandleWither.java | 2 +-
src/core/lombok/experimental/Builder.java | 16 ++++++++++
src/core/lombok/javac/handlers/HandleBuilder.java | 33 +++++++++++++++-----
.../lombok/javac/handlers/HandleFieldDefaults.java | 10 ++++++-
.../lombok/javac/handlers/JavacHandlerUtil.java | 29 +++++++++++++++++-
test/core/src/lombok/AbstractRunTests.java | 18 ++++++-----
.../after-delombok/BuilderChainAndFluent.java | 31 +++++++++++++++++++
.../resource/after-delombok/BuilderSimple.java | 2 +-
.../resource/after-ecj/BuilderChainAndFluent.java | 25 ++++++++++++++++
.../resource/after-ecj/BuilderSimple.java | 2 +-
.../resource/before/BuilderChainAndFluent.java | 4 +++
.../resource/before/BuilderInvalidUse.java | 18 +++++++++++
.../BuilderInvalidUse.java.messages | 2 ++
.../messages-ecj/BuilderInvalidUse.java.messages | 2 ++
website/features/Value.html | 5 +++-
website/features/experimental/Builder.html | 33 ++++++++++++--------
website/features/experimental/index.html | 2 +-
23 files changed, 290 insertions(+), 48 deletions(-)
create mode 100644 test/transform/resource/after-delombok/BuilderChainAndFluent.java
create mode 100644 test/transform/resource/after-ecj/BuilderChainAndFluent.java
create mode 100644 test/transform/resource/before/BuilderChainAndFluent.java
create mode 100644 test/transform/resource/before/BuilderInvalidUse.java
create mode 100644 test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages
create mode 100644 test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages
(limited to 'test/transform/resource/messages-delombok')
diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java
index 921c27d6..8959ad7a 100644
--- a/src/core/lombok/core/TransformationsUtil.java
+++ b/src/core/lombok/core/TransformationsUtil.java
@@ -22,13 +22,25 @@
package lombok.core;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.Value;
import lombok.experimental.Accessors;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.Wither;
/**
* Container for static utility methods useful for some of the standard lombok transformations, regardless of
@@ -39,6 +51,13 @@ public class TransformationsUtil {
//Prevent instantiation
}
+ @SuppressWarnings({"all", "unchecked", "deprecation"})
+ public static final List> INVALID_ON_BUILDERS = Collections.unmodifiableList(
+ Arrays.>asList(
+ Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class,
+ RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class,
+ Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class));
+
/**
* Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list.
* For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if
@@ -159,12 +178,12 @@ public class TransformationsUtil {
if (fieldName.length() == 0) return null;
- Accessors ac = accessors.getInstance();
- fieldName = removePrefix(fieldName, ac.prefix());
+ Accessors ac = accessors == null ? null : accessors.getInstance();
+ fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix());
if (fieldName == null) return null;
String fName = fieldName.toString();
- if (adhereToFluent && ac.fluent()) return fName;
+ if (adhereToFluent && ac != null && ac.fluent()) return fName;
if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) {
// The field is for example named 'isRunning'.
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index 364ce0a5..9bd634f7 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -22,6 +22,7 @@
package lombok.eclipse.handlers;
import static lombok.eclipse.Eclipse.*;
+import static lombok.core.TransformationsUtil.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -296,6 +297,29 @@ public class EclipseHandlerUtil {
}
+ public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) {
+ List disallowed = null;
+ for (EclipseNode child : typeNode.down()) {
+ for (Class extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
+ if (annotationTypeMatches(annType, child)) {
+ if (disallowed == null) disallowed = new ArrayList();
+ disallowed.add(annType.getSimpleName());
+ }
+ }
+ }
+
+ int size = disallowed == null ? 0 : disallowed.size();
+ if (size == 0) return;
+ if (size == 1) {
+ errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes.");
+ return;
+ }
+ StringBuilder out = new StringBuilder();
+ for (String a : disallowed) out.append("@").append(a).append(", ");
+ out.setLength(out.length() - 2);
+ errorNode.addError(out.append(" are not allowed on builder classes.").toString());
+ }
+
public static Annotation copyAnnotation(Annotation annotation, ASTNode source) {
int pS = source.sourceStart, pE = source.sourceEnd;
@@ -845,15 +869,20 @@ public class EclipseHandlerUtil {
private static final Object MARKER = new Object();
static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) {
- if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return;
- generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
+ if (isBoolean(returnType)) {
+ generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
+ }
+ }
+
+ public static boolean isBoolean(TypeReference typeReference) {
+ return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0;
}
private static GetterMethod findGetter(EclipseNode field) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get();
boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration);
TypeReference fieldType = fieldDeclaration.type;
- boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0);
+ boolean isBoolean = forceBool || isBoolean(fieldType);
EclipseNode typeNode = field.up();
for (String potentialGetterName : toAllGetterNames(field, isBoolean)) {
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index e2bf5fe2..70110a9c 100644
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -58,13 +58,17 @@ import org.mangosdk.spi.ProviderFor;
import lombok.AccessLevel;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.TransformationsUtil;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.Builder;
+import lombok.experimental.NonFinal;
@ProviderFor(EclipseAnnotationHandler.class)
+@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes.
public class HandleBuilder extends EclipseAnnotationHandler {
@Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) {
long p = (long) ast.sourceStart << 32 | ast.sourceEnd;
@@ -99,14 +103,23 @@ public class HandleBuilder extends EclipseAnnotationHandler {
if (parent.get() instanceof TypeDeclaration) {
tdParent = parent;
TypeDeclaration td = (TypeDeclaration) tdParent.get();
- new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.emptyList(), ast);
+ List fields = new ArrayList();
+ @SuppressWarnings("deprecation")
+ boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
FieldDeclaration fd = (FieldDeclaration) fieldNode.get();
+ // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes
+ // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves.
+ // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that.
+ if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
namesOfParameters.add(fd.name);
typesOfParameters.add(fd.type);
+ fields.add(fieldNode);
}
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.emptyList(), ast);
+
returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
typeParams = td.typeParameters;
thrownExceptions = null;
@@ -181,11 +194,15 @@ public class HandleBuilder extends EclipseAnnotationHandler {
}
EclipseNode builderType = findInnerClass(tdParent, builderClassName);
- if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ if (builderType == null) {
+ builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ } else {
+ sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
+ }
List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
List newMethods = new ArrayList();
for (EclipseNode fieldNode : fieldNodes) {
- MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
+ MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain());
if (newMethod != null) newMethods.add(newMethod);
}
@@ -315,7 +332,7 @@ public class HandleBuilder extends EclipseAnnotationHandler {
private static final AbstractMethodDeclaration[] EMPTY = {};
- private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source) {
+ private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) {
TypeDeclaration td = (TypeDeclaration) builderType.get();
AbstractMethodDeclaration[] existing = td.methods;
if (existing == null) existing = EMPTY;
@@ -329,7 +346,10 @@ public class HandleBuilder extends EclipseAnnotationHandler {
if (Arrays.equals(name, existingName)) return null;
}
- return HandleSetter.createSetter(td, fieldNode, fieldNode.getName(), true, ClassFileConstants.AccPublic,
+ boolean isBoolean = isBoolean(fd.type);
+ String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
+
+ return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic,
source, Collections.emptyList(), Collections.emptyList());
}
diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
index 0d21fc08..d6d839cc 100644
--- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
+++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
@@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
-@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
+@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends EclipseAnnotationHandler {
public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler
return;
}
+ if (level == AccessLevel.PACKAGE) {
+ annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
+ }
+
+ if (!makeFinal && annotation.isExplicit("makeFinal")) {
+ annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
+ }
+
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java
index 760c595e..787f6f6c 100644
--- a/src/core/lombok/eclipse/handlers/HandleGetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleGetter.java
@@ -187,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler {
}
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String getterName = toGetterName(fieldNode, isBoolean);
if (getterName == null) {
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
index ae846a4e..3bfcc51c 100644
--- a/src/core/lombok/eclipse/handlers/HandleSetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String setterName = toSetterName(fieldNode, isBoolean);
boolean shouldReturnThis = shouldReturnThis(fieldNode);
diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java
index 9d74cbd1..27fbc635 100644
--- a/src/core/lombok/eclipse/handlers/HandleWither.java
+++ b/src/core/lombok/eclipse/handlers/HandleWither.java
@@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String witherName = toWitherName(fieldNode, isBoolean);
if (witherName == null) {
diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java
index 5f2d1ca6..1300e7d3 100644
--- a/src/core/lombok/experimental/Builder.java
+++ b/src/core/lombok/experimental/Builder.java
@@ -118,4 +118,20 @@ public @interface Builder {
* Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}.
*/
String builderClassName() default "";
+
+ /**
+ * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this
+ * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}.
+ *
+ * Default: true
+ */
+ boolean fluent() default true;
+
+ /**
+ * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain
+ * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead.
+ *
+ * Default: true
+ */
+ boolean chain() default true;
}
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index aa485b26..6422f5ed 100644
--- a/src/core/lombok/javac/handlers/HandleBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -49,16 +49,19 @@ import com.sun.tools.javac.util.Name;
import lombok.AccessLevel;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.TransformationsUtil;
import lombok.experimental.Builder;
+import lombok.experimental.NonFinal;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
-
import static lombok.javac.Javac.*;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.javac.handlers.JavacHandlerUtil.*;
@ProviderFor(JavacAnnotationHandler.class)
+@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes.
public class HandleBuilder extends JavacAnnotationHandler {
@Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) {
Builder builderInstance = annotation.getInstance();
@@ -94,14 +97,22 @@ public class HandleBuilder extends JavacAnnotationHandler {
if (parent.get() instanceof JCClassDecl) {
tdParent = parent;
JCClassDecl td = (JCClassDecl) tdParent.get();
- new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode);
-
+ ListBuffer allFields = ListBuffer.lb();
+ @SuppressWarnings("deprecation")
+ boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
+ // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes
+ // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves.
+ // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that.
+ if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
namesOfParameters.add(fd.name);
typesOfParameters.add(fd.vartype);
+ allFields.append(fieldNode);
}
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode);
+
returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
typeParams = td.typarams;
thrownExceptions = List.nil();
@@ -171,11 +182,15 @@ public class HandleBuilder extends JavacAnnotationHandler {
}
JavacNode builderType = findInnerClass(tdParent, builderClassName);
- if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ if (builderType == null) {
+ builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ } else {
+ sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
+ }
java.util.List fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
java.util.List newMethods = new ArrayList();
for (JavacNode fieldNode : fieldNodes) {
- JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
+ JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain());
if (newMethod != null) newMethods.add(newMethod);
}
@@ -281,16 +296,20 @@ public class HandleBuilder extends JavacAnnotationHandler {
}
- private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) {
+ private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source, boolean fluent, boolean chain) {
Name fieldName = ((JCVariableDecl) fieldNode.get()).name;
+
for (JavacNode child : builderType.down()) {
if (child.getKind() != Kind.METHOD) continue;
Name existingName = ((JCMethodDecl) child.get()).name;
if (existingName.equals(fieldName)) return null;
}
+ boolean isBoolean = isBoolean(fieldNode);
+ String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
+
TreeMaker maker = builderType.getTreeMaker();
- return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.nil(), List.nil());
+ return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.nil(), List.nil());
}
private JavacNode findInnerClass(JavacNode parent, String name) {
diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java
index d32446c3..038f3e3f 100644
--- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java
+++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java
@@ -44,7 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(JavacAnnotationHandler.class)
-@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
+@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends JavacAnnotationHandler {
public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler {
return;
}
+ if (level == AccessLevel.PACKAGE) {
+ annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
+ }
+
+ if (!makeFinal && annotation.isExplicit("makeFinal")) {
+ annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
+ }
+
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
index a24dad7d..d7d29da2 100644
--- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -21,6 +21,7 @@
*/
package lombok.javac.handlers;
+import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS;
import static lombok.javac.Javac.*;
import java.lang.annotation.Annotation;
@@ -446,8 +447,12 @@ public class JavacHandlerUtil {
}
}
- private static boolean isBoolean(JavacNode field) {
+ public static boolean isBoolean(JavacNode field) {
JCExpression varType = ((JCVariableDecl) field.get()).vartype;
+ return isBoolean(varType);
+ }
+
+ public static boolean isBoolean(JCExpression varType) {
return varType != null && varType.toString().equals("boolean");
}
@@ -1065,6 +1070,28 @@ public class JavacHandlerUtil {
return maker.Ident(typeName);
}
+ public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) {
+ List disallowed = List.nil();
+ for (JavacNode child : typeNode.down()) {
+ for (Class extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
+ if (annotationTypeMatches(annType, child)) {
+ disallowed = disallowed.append(annType.getSimpleName());
+ }
+ }
+ }
+
+ int size = disallowed.size();
+ if (size == 0) return;
+ if (size == 1) {
+ errorNode.addError("@" + disallowed.head + " is not allowed on builder classes.");
+ return;
+ }
+ StringBuilder out = new StringBuilder();
+ for (String a : disallowed) out.append("@").append(a).append(", ");
+ out.setLength(out.length() - 2);
+ errorNode.addError(out.append(" are not allowed on builder classes.").toString());
+ }
+
static List copyAnnotations(List extends JCExpression> in) {
ListBuffer out = ListBuffer.lb();
for (JCExpression expr : in) {
diff --git a/test/core/src/lombok/AbstractRunTests.java b/test/core/src/lombok/AbstractRunTests.java
index a80c7d8d..2f3f0988 100644
--- a/test/core/src/lombok/AbstractRunTests.java
+++ b/test/core/src/lombok/AbstractRunTests.java
@@ -68,10 +68,12 @@ public abstract class AbstractRunTests {
}
}
- StringReader r = new StringReader(expectedFile);
- BufferedReader br = new BufferedReader(r);
- String firstLine = br.readLine();
- if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false;
+ if (expectedFile != null) {
+ StringReader r = new StringReader(expectedFile);
+ BufferedReader br = new BufferedReader(r);
+ String firstLine = br.readLine();
+ if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false;
+ }
compare(
file.getName(),
@@ -91,7 +93,7 @@ public abstract class AbstractRunTests {
try {
reader = new BufferedReader(new FileReader(file));
} catch (FileNotFoundException e) {
- return "";
+ return null;
}
StringBuilder result = new StringBuilder();
String line;
@@ -104,7 +106,7 @@ public abstract class AbstractRunTests {
}
private String readFile(File dir, File file, boolean messages) throws IOException {
- if (dir == null) return "";
+ if (dir == null) return null;
return readFile(new File(dir, file.getName() + (messages ? ".messages" : "")));
}
@@ -140,7 +142,9 @@ public abstract class AbstractRunTests {
}
private void compare(String name, String expectedFile, String actualFile, List expectedMessages, LinkedHashSet actualMessages, boolean printErrors) throws Throwable {
- try {
+ if (expectedFile == null && expectedMessages.isEmpty()) expectedFile = "";
+
+ if (expectedFile != null) try {
compareContent(name, expectedFile, actualFile);
} catch (Throwable e) {
if (printErrors) {
diff --git a/test/transform/resource/after-delombok/BuilderChainAndFluent.java b/test/transform/resource/after-delombok/BuilderChainAndFluent.java
new file mode 100644
index 00000000..d4975bff
--- /dev/null
+++ b/test/transform/resource/after-delombok/BuilderChainAndFluent.java
@@ -0,0 +1,31 @@
+class BuilderChainAndFluent {
+ private final int yes;
+ @java.lang.SuppressWarnings("all")
+ BuilderChainAndFluent(final int yes) {
+ this.yes = yes;
+ }
+ @java.lang.SuppressWarnings("all")
+ public static class BuilderChainAndFluentBuilder {
+ private int yes;
+ @java.lang.SuppressWarnings("all")
+ BuilderChainAndFluentBuilder() {
+ }
+ @java.lang.SuppressWarnings("all")
+ public void setYes(final int yes) {
+ this.yes = yes;
+ }
+ @java.lang.SuppressWarnings("all")
+ public BuilderChainAndFluent build() {
+ return new BuilderChainAndFluent(yes);
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public java.lang.String toString() {
+ return "BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ public static BuilderChainAndFluentBuilder builder() {
+ return new BuilderChainAndFluentBuilder();
+ }
+}
\ No newline at end of file
diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java
index 24ac369c..11c0e58c 100644
--- a/test/transform/resource/after-delombok/BuilderSimple.java
+++ b/test/transform/resource/after-delombok/BuilderSimple.java
@@ -5,7 +5,7 @@ class BuilderSimple {
private List also;
private int $butNotMe;
@java.lang.SuppressWarnings("all")
- private BuilderSimple(final int yes, final List also) {
+ BuilderSimple(final int yes, final List also) {
this.yes = yes;
this.also = also;
}
diff --git a/test/transform/resource/after-ecj/BuilderChainAndFluent.java b/test/transform/resource/after-ecj/BuilderChainAndFluent.java
new file mode 100644
index 00000000..6a307105
--- /dev/null
+++ b/test/transform/resource/after-ecj/BuilderChainAndFluent.java
@@ -0,0 +1,25 @@
+@lombok.experimental.Builder(fluent = false,chain = false) class BuilderChainAndFluent {
+ public static @java.lang.SuppressWarnings("all") class BuilderChainAndFluentBuilder {
+ private int yes;
+ @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder() {
+ super();
+ }
+ public @java.lang.SuppressWarnings("all") void setYes(final int yes) {
+ this.yes = yes;
+ }
+ public @java.lang.SuppressWarnings("all") BuilderChainAndFluent build() {
+ return new BuilderChainAndFluent(yes);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
+ return (("BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes) + ")");
+ }
+ }
+ private final int yes;
+ @java.lang.SuppressWarnings("all") BuilderChainAndFluent(final int yes) {
+ super();
+ this.yes = yes;
+ }
+ public static @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder builder() {
+ return new BuilderChainAndFluentBuilder();
+ }
+}
diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java
index 228b1928..85db360d 100644
--- a/test/transform/resource/after-ecj/BuilderSimple.java
+++ b/test/transform/resource/after-ecj/BuilderSimple.java
@@ -25,7 +25,7 @@ import java.util.List;
private final int yes;
private List also;
private int $butNotMe;
- private @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List also) {
+ @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List also) {
super();
this.yes = yes;
this.also = also;
diff --git a/test/transform/resource/before/BuilderChainAndFluent.java b/test/transform/resource/before/BuilderChainAndFluent.java
new file mode 100644
index 00000000..4d08741b
--- /dev/null
+++ b/test/transform/resource/before/BuilderChainAndFluent.java
@@ -0,0 +1,4 @@
+@lombok.experimental.Builder(fluent = false, chain = false)
+class BuilderChainAndFluent {
+ private final int yes;
+}
diff --git a/test/transform/resource/before/BuilderInvalidUse.java b/test/transform/resource/before/BuilderInvalidUse.java
new file mode 100644
index 00000000..07f37d3d
--- /dev/null
+++ b/test/transform/resource/before/BuilderInvalidUse.java
@@ -0,0 +1,18 @@
+@lombok.experimental.Builder
+class BuilderInvalidUse {
+ private int something;
+
+ @lombok.Getter @lombok.Setter @lombok.experimental.FieldDefaults(makeFinal = true) @lombok.experimental.Wither @lombok.Data @lombok.ToString @lombok.EqualsAndHashCode
+ @lombok.AllArgsConstructor
+ public static class BuilderInvalidUseBuilder {
+
+ }
+}
+
+@lombok.experimental.Builder
+class AlsoInvalid {
+ @lombok.Value
+ public static class AlsoInvalidBuilder {
+
+ }
+}
\ No newline at end of file
diff --git a/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages
new file mode 100644
index 00000000..aeeb0c86
--- /dev/null
+++ b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages
@@ -0,0 +1,2 @@
+1:1 @Getter, @Setter, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes.
+12:1 @Value is not allowed on builder classes.
\ No newline at end of file
diff --git a/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages
new file mode 100644
index 00000000..8ffc6e26
--- /dev/null
+++ b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages
@@ -0,0 +1,2 @@
+1:0 @Getter, @Setter, @FieldDefaults, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes.
+12:331 @Value is not allowed on builder classes.
\ No newline at end of file
diff --git a/website/features/Value.html b/website/features/Value.html
index 92fcc825..e2cd8600 100644
--- a/website/features/Value.html
+++ b/website/features/Value.html
@@ -31,7 +31,7 @@
It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
It is possible to override any default behaviour for any of the 'parts' that make up @Value by explicitly using that annotation.
-
+
@@ -54,6 +54,9 @@
@Value was an experimental feature from v0.11.4 to v0.11.9 (as @lombok.experimental.Value). It has since been moved into the core package. The old annotation is still
around (and is an alias). It will eventually be removed in a future version, though.
+
+ It is not possible to use @FieldDefaults to 'undo' the private-by-default and final-by-default aspect of fields in the annotated class. Use @NonFinal and @PackagePrivate on the fields in the class to override this behaviour.
+
diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html
index 31fcd5ad..16d58050 100644
--- a/website/features/experimental/index.html
+++ b/website/features/experimental/index.html
@@ -23,7 +23,7 @@
as a core feature and move out of the experimental package.