Fixes #1197, add Objects.requireNonNull and Preconditions.checkkNotNull to supported null-check styles

This commit is contained in:
Roel Spilker committed 2019-07-16 02:04:11 +02:00
1 parent e1d1415d6e
commit aa80e1baf9
12 files changed
+356 -34

No files matched your search

+2
View File
@@ -7,11 +7,13 @@ Lombok Changelog
* ENHANCEMENT: `val` is now capable of decoding the type of convoluted expressions (particularly if the right hand side involves lambdas and conditional (ternary) expressions). [Pull Request #2109](https://github.com/rzwitserloot/lombok/pull/2109) and [Pull Request #2138](https://github.com/rzwitserloot/lombok/pull/2138) with thanks to Alexander Bulgakov.
* ENHANCEMENT: You can now configure the generated builder class name via the config system, using key `lombok.builder.className`. See the [Builder documentation](https://projectlombok.org/features/Builder) and [SuperBuilder documentation](https://projectlombok.org/features/experimental/SuperBuilder)
* ENHANCEMENT: If you mix up eclipse's non-null support, such as `@NonNullByDefault`, with lombok's `@NonNull`, you get a bunch of warnings about dead code that are inappropriate. These warnings are now suppressed, thanks to a contribution from Till Brychcy! [Pull Request #2155](https://github.com/rzwitserloot/lombok/pull/2155)
* ENHANCEMENT: `@NonNull` can now also generate checks using jdk's `Objects.requireNonNull` or Guava's `Preconditions.checkNotNull`. [Issue #1197](https://github.com/rzwitserloot/lombok/issues/1197)
* BUGFIX: Delombok would turn something like `List<byte[]>...` in a method parameter to `List<byte...>...` [Issue #2140](https://github.com/rzwitserloot/lombok/issues/2140)
* BUGFIX: Javac would generate the wrong equals and hashCode if a type-use annotation was put on an array type field [Issue #2165](https://github.com/rzwitserloot/lombok/issues/2165)
* BUGFIX: Eclipse 2019-06 + JDK-12 compatibility + an `@Singular` builder entry would produce a cascade of error dialogs. [Issue #2169](https://github.com/rzwitserloot/lombok/issues/2169)
* IMPROBABLE BREAKING CHANGE: Stricter validation of configuration keys dealing with identifiers and types (`lombok.log.fieldName`, `lombok.fieldNameConstants.innerTypeName`, `lombok.copyableAnnotations`).
* IMPROBABLE BREAKING CHANGE: The fields generated inside builders for fields with defaults (with `@Builder` on a class with fields marked `@Default`) now have `$value` as the name; direct manipulation of these fields is not advised because there is an associated `$set` variable that also needs to be taken into account. [Issue #2115](https://github.com/rzwitserloot/lombok/issues/2115)
### v1.18.8 (May 7th, 2019)
* FEATURE: You can now configure `@FieldNameConstants` to `CONSTANT_CASE` the generated constants, using a `lombok.config` option. See the [FieldNameConstants documentation](https://projectlombok.org/features/experimental/FieldNameConstants). [Issue #2092](https://github.com/rzwitserloot/lombok/issues/2092).
* FEATURE: You can now suppress generation of the `builder` method when using `@Builder`; usually because you're only interested in the `toBuilder` method. As a convenience we won't emit warnings about missing `@Builder.Default` annotations when you do this. [Issue #2046](https://github.com/rzwitserloot/lombok/issues/2046)
@@ -21,28 +21,64 @@
*/
package lombok.core.configuration;
import lombok.core.LombokImmutableList;
@ExampleValueString("[NullPointerException | IllegalArgumentException | Assertion]")
@ExampleValueString("[NullPointerException | IllegalArgumentException | Assertion | JDK | GUAVA]")
public enum NullCheckExceptionType {
ILLEGAL_ARGUMENT_EXCEPTION {
@Override public String getExceptionType() {
return "java.lang.IllegalArgumentException";
}
@Override public LombokImmutableList<String> getMethod() {
return null;
}
},
NULL_POINTER_EXCEPTION {
@Override public String getExceptionType() {
return "java.lang.NullPointerException";
}
@Override public LombokImmutableList<String> getMethod() {
return null;
}
},
ASSERTION {
@Override public String getExceptionType() {
return null;
}
@Override public LombokImmutableList<String> getMethod() {
return null;
}
},
JDK {
@Override public String getExceptionType() {
return null;
}
@Override public LombokImmutableList<String> getMethod() {
return METHOD_JDK;
}
},
GUAVA {
@Override public String getExceptionType() {
return null;
}
@Override public LombokImmutableList<String> getMethod() {
return METHOD_GUAVA;
}
};
private static final LombokImmutableList<String> METHOD_JDK = LombokImmutableList.of("java", "util", "Objects", "requireNonNull");
private static final LombokImmutableList<String> METHOD_GUAVA = LombokImmutableList.of("com", "google", "common", "base", "Preconditions", "checkNotNull");
public String toExceptionMessage(String fieldName) {
return fieldName + " is marked non-null but is null";
}
public abstract String getExceptionType();
public abstract LombokImmutableList<String> getMethod();
}
@@ -38,26 +38,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.Data;
import lombok.Getter;
import lombok.Lombok;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.TypeResolver;
import lombok.core.configuration.NullCheckExceptionType;
import lombok.core.configuration.TypeName;
import lombok.core.debug.ProblemReporter;
import lombok.core.handlers.HandlerUtil;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAST;
import lombok.eclipse.EclipseNode;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import lombok.permit.Permit;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
@@ -121,6 +101,28 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.Data;
import lombok.Getter;
import lombok.Lombok;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.LombokImmutableList;
import lombok.core.TypeResolver;
import lombok.core.configuration.NullCheckExceptionType;
import lombok.core.configuration.TypeName;
import lombok.core.debug.ProblemReporter;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.HandlerUtil.FieldAccess;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAST;
import lombok.eclipse.EclipseNode;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import lombok.permit.Permit;
/**
* Container for static utility methods useful to handlers written for eclipse.
*/
@@ -1818,8 +1820,6 @@ public class EclipseHandlerUtil {
/**
* Generates a new statement that checks if the given local variable is null, and if so, throws a specified exception with the
* variable name as message.
*
* @param exName The name of the exception to throw; normally {@code java.lang.NullPointerException}.
*/
public static Statement generateNullCheck(TypeReference type, char[] variable, EclipseNode sourceNode) {
NullCheckExceptionType exceptionType = sourceNode.getAst().readConfiguration(ConfigurationKeys.NON_NULL_EXCEPTION_TYPE);
@@ -1828,24 +1828,44 @@ public class EclipseHandlerUtil {
ASTNode source = sourceNode.get();
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
long p = (long) pS << 32 | pE;
if (isPrimitive(type)) return null;
SingleNameReference varName = new SingleNameReference(variable, p);
setGeneratedBy(varName, source);
StringLiteral message = new StringLiteral(exceptionType.toExceptionMessage(new String(variable)).toCharArray(), pS, pE, 0);
setGeneratedBy(message, source);
LombokImmutableList<String> method = exceptionType.getMethod();
if (method != null) {
MessageSend invocation = new MessageSend();
invocation.sourceStart = pS; invocation.sourceEnd = pE;
setGeneratedBy(invocation, source);
char[][] utilityTypeName = new char[method.size() - 1][];
for (int i = 0; i < method.size() - 1; i++) {
utilityTypeName[i] = method.get(i).toCharArray();
}
invocation.receiver = new QualifiedNameReference(utilityTypeName, new long[method.size()], pS, pE);
setGeneratedBy(invocation.receiver, source);
invocation.selector = method.get(method.size() - 1).toCharArray();
invocation.arguments = new Expression[] {varName, message};
return invocation;
}
AllocationExpression exception = new AllocationExpression();
setGeneratedBy(exception, source);
SingleNameReference varName = new SingleNameReference(variable, p);
setGeneratedBy(varName, source);
NullLiteral nullLiteral = new NullLiteral(pS, pE);
setGeneratedBy(nullLiteral, source);
int equalOperator = exceptionType == NullCheckExceptionType.ASSERTION ? OperatorIds.NOT_EQUAL : OperatorIds.EQUAL_EQUAL;
EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, equalOperator);
equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE;
setGeneratedBy(equalExpression, source);
StringLiteral message = new StringLiteral(exceptionType.toExceptionMessage(new String(variable)).toCharArray(), pS, pE, 0);
setGeneratedBy(message, source);
if (exceptionType == NullCheckExceptionType.ASSERTION) {
Statement assertStatement = new AssertStatement(message, equalExpression, pS);
@@ -1865,7 +1885,6 @@ public class EclipseHandlerUtil {
ThrowStatement throwStatement = new ThrowStatement(exception, pS, pE);
setGeneratedBy(throwStatement, source);
Block throwBlock = new Block(0);
throwBlock.statements = new Statement[] {throwStatement};
throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE;
@@ -33,10 +33,12 @@ 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.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
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.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
@@ -61,6 +63,9 @@ import lombok.eclipse.EclipseNode;
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(value = 512) // 2^9; onParameter=@__(@NonNull) has to run first.
public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
private static final char[] REQUIRE_NON_NULL = "requireNonNull".toCharArray();
private static final char[] CHECK_NOT_NULL = "checkNotNull".toCharArray();
public static final HandleNonNull INSTANCE = new HandleNonNull();
public void fix(EclipseNode method) {
@@ -193,7 +198,22 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
public char[] returnVarNameIfNullCheck(Statement stat) {
boolean isIf = stat instanceof IfStatement;
if (!isIf && !(stat instanceof AssertStatement)) return null;
boolean isExpression = stat instanceof Expression;
if (!isIf && !(stat instanceof AssertStatement) && !isExpression) return null;
if (isExpression) {
/* Check if the statements contains a call to checkNotNull or requireNonNull */
Expression expression = (Expression) stat;
if (expression instanceof Assignment) expression = ((Assignment) expression).expression;
if (!(expression instanceof MessageSend)) return null;
MessageSend invocation = (MessageSend) expression;
if (!Arrays.equals(invocation.selector, CHECK_NOT_NULL) && !Arrays.equals(invocation.selector, REQUIRE_NON_NULL)) return null;
if (invocation.arguments == null || invocation.arguments.length == 0) return null;
Expression firstArgument = invocation.arguments[0];
if (!(firstArgument instanceof SingleNameReference)) return null;
return ((SingleNameReference) firstArgument).token;
}
if (isIf) {
/* Check that the if's statement is a throw statement, possibly in a block. */
@@ -31,13 +31,17 @@ import org.mangosdk.spi.ProviderFor;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssert;
import com.sun.tools.javac.tree.JCTree.JCAssign;
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.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
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.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCParens;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
@@ -45,6 +49,7 @@ import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import lombok.ConfigurationKeys;
import lombok.NonNull;
@@ -167,8 +172,31 @@ public class HandleNonNull extends JavacAnnotationHandler<NonNull> {
* If it is not of this form, returns null.
*/
public String returnVarNameIfNullCheck(JCStatement stat) {
boolean isIf = stat instanceof JCIf;
if (!isIf && !(stat instanceof JCAssert)) return null;
boolean isIf = stat instanceof JCIf;
boolean isExpression = stat instanceof JCExpressionStatement;
if (!isIf && !(stat instanceof JCAssert) && !isExpression) return null;
if (isExpression) {
/* Check if the statements contains a call to checkNotNull or requireNonNull */
JCExpression expression = ((JCExpressionStatement) stat).expr;
if (expression instanceof JCAssign) expression = ((JCAssign) expression).rhs;
if (!(expression instanceof JCMethodInvocation)) return null;
JCMethodInvocation invocation = (JCMethodInvocation) expression;
JCExpression method = invocation.meth;
Name name = null;
if (method instanceof JCFieldAccess) {
name = ((JCFieldAccess) method).name;
} else if (method instanceof JCIdent) {
name = ((JCIdent) method).name;
}
if (name == null || (!name.contentEquals("checkNotNull") && !name.contentEquals("requireNonNull"))) return null;
if (invocation.args.isEmpty()) return null;
JCExpression firstArgument = invocation.args.head;
if (!(firstArgument instanceof JCIdent)) return null;
return ((JCIdent) firstArgument).toString();
}
if (isIf) {
/* Check that the if's statement is a throw statement, possibly in a block. */
@@ -1529,6 +1529,12 @@ public class JavacHandlerUtil {
if (isPrimitive(typeNode)) return null;
JCLiteral message = maker.Literal(exceptionType.toExceptionMessage(varName.toString()));
LombokImmutableList<String> method = exceptionType.getMethod();
if (method != null) {
return maker.Exec(maker.Apply(List.<JCExpression>nil(), chainDots(source, method), List.of(maker.Ident(varName), message)));
}
if (exceptionType == NullCheckExceptionType.ASSERTION) {
return maker.Assert(maker.Binary(CTC_NOT_EQUAL, maker.Ident(varName), maker.Literal(CTC_BOT, null)), message);
}
@@ -0,0 +1,35 @@
import static com.google.common.base.Preconditions.*;
public class NonNullWithGuava {
@lombok.NonNull
private String test;
public void testMethod(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
checkNotNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = checkNotNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
checkNotNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
com.google.common.base.Preconditions.checkNotNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
test = checkNotNull(test);
}
@java.lang.SuppressWarnings("all")
public void setTest(@lombok.NonNull final String test) {
com.google.common.base.Preconditions.checkNotNull(test, "test is marked non-null but is null");
this.test = test;
}
}
@@ -0,0 +1,36 @@
//version 7:
import static java.util.Objects.*;
public class NonNullWithJdk {
@lombok.NonNull
private String test;
public void testMethod(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
requireNonNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = requireNonNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
requireNonNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
java.util.Objects.requireNonNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
test = requireNonNull(test);
}
@java.lang.SuppressWarnings("all")
public void setTest(@lombok.NonNull final String test) {
java.util.Objects.requireNonNull(test, "test is marked non-null but is null");
this.test = test;
}
}
@@ -0,0 +1,36 @@
import static com.google.common.base.Preconditions.*;
public class NonNullWithGuava {
private @lombok.NonNull @lombok.Setter String test;
public NonNullWithGuava() {
super();
}
public void testMethod(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
checkNotNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = checkNotNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
checkNotNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
com.google.common.base.Preconditions.checkNotNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg, "arg is marked non-null but is null");
test = checkNotNull(test);
}
public @java.lang.SuppressWarnings("all") void setTest(final @lombok.NonNull String test) {
com.google.common.base.Preconditions.checkNotNull(test, "test is marked non-null but is null");
this.test = test;
}
}
@@ -0,0 +1,37 @@
//version 7:
import static java.util.Objects.*;
public class NonNullWithJdk {
private @lombok.NonNull @lombok.Setter String test;
public NonNullWithJdk() {
super();
}
public void testMethod(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
requireNonNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = requireNonNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
requireNonNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
java.util.Objects.requireNonNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg, "arg is marked non-null but is null");
test = requireNonNull(test);
}
public @java.lang.SuppressWarnings("all") void setTest(final @lombok.NonNull String test) {
java.util.Objects.requireNonNull(test, "test is marked non-null but is null");
this.test = test;
}
}
@@ -0,0 +1,33 @@
//CONF: lombok.nonNull.exceptionType = Guava
import static com.google.common.base.Preconditions.*;
public class NonNullWithGuava {
@lombok.NonNull @lombok.Setter private String test;
public void testMethod(@lombok.NonNull String arg) {
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
checkNotNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = checkNotNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
checkNotNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
com.google.common.base.Preconditions.checkNotNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
test = checkNotNull(test);
}
}
@@ -0,0 +1,34 @@
//version 7:
//CONF: lombok.nonNull.exceptionType = Jdk
import static java.util.Objects.*;
public class NonNullWithJdk {
@lombok.NonNull @lombok.Setter private String test;
public void testMethod(@lombok.NonNull String arg) {
System.out.println(arg);
}
public void testMethodWithCheck1(@lombok.NonNull String arg) {
requireNonNull(arg);
}
public void testMethodWithCheckAssign(@lombok.NonNull String arg) {
test = requireNonNull(arg);
}
public void testMethodWithCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(arg);
}
public void testMethodWithFakeCheck1(@lombok.NonNull String arg) {
requireNonNull("");
}
public void testMethodWithFakeCheck2(@lombok.NonNull String arg) {
java.util.Objects.requireNonNull(test);
}
public void testMethodWithFakeCheckAssign(@lombok.NonNull String arg) {
test = requireNonNull(test);
}
}