diff options
Diffstat (limited to 'src')
13 files changed, 252 insertions, 44 deletions
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index ef433d8d..691346cd 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2019 The Project Lombok Authors. + * Copyright (C) 2013-2020 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 @@ -23,12 +23,13 @@ package lombok; import java.util.List; +import lombok.Singular.NullCollectionBehavior; import lombok.core.configuration.CallSuperType; import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.configuration.ConfigurationKey; -import lombok.core.configuration.LogDeclaration; import lombok.core.configuration.FlagUsageType; import lombok.core.configuration.IdentifierName; +import lombok.core.configuration.LogDeclaration; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.configuration.TypeName; @@ -287,7 +288,19 @@ public class ConfigurationKeys { * * By default or if explicitly set to {@code true}, lombok will attempt to automatically singularize the name of your variable/parameter when using {@code @Singular}; the name is assumed to be written in english, and a plural. If explicitly to {@code false}, you must always specify the singular form; this is especially useful if your identifiers are in a foreign language. */ - public static final ConfigurationKey<Boolean> SINGULAR_AUTO = new ConfigurationKey<Boolean>("lombok.singular.auto", "If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using {@code @Singular}.") {}; + public static final ConfigurationKey<Boolean> SINGULAR_AUTO = new ConfigurationKey<Boolean>("lombok.singular.auto", "If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using @Singular.") {}; + + /** + * lombok configuration: {@code lombok.singular.nullCollections} = one of: [{@code NullPointerException}, {@code IllegalArgumentException}, {@code JDK}, {@code Guava}, or {@code ignore}]. + * + * Lombok generates a 'plural form' method, which takes in a collection and will <em>add</em> each element in it to the {@code @Singular} marked target. What should happen if {@code null} is passed instead of a collection instance?<ul> + * <li>If the chosen configuration is {@code NullPointerException} (the default), or {@code IllegalArgumentException}, that exception type is a thrown, with as message <code><em>field-name</em> must not be null</code>.</li> + * <li>If the chosen configuration is {@code JDK}, a call to {@code java.util.Objects.requireNonNull} is generated with the fieldname passed along (which throws {@code NullPointerException}).</li> + * <li>If the chosen configuration is {@code Guava}, a call to {@code com.google.common.base.Preconditions.checkNotNull} is generated with the fieldname passed along (which throws {@code NullPointerException}).</li> + * <li>If the chosen configuration is {@code Ignore}, then no exception occurs, and the call does nothing; it acts as if an empty collection was passed.</li> + * </ul> + */ + public static final ConfigurationKey<NullCollectionBehavior> SINGULAR_NULL_COLLECTIONS = new ConfigurationKey<NullCollectionBehavior>("lombok.singular.nullCollections", "Lombok generates a method to add all elements in a collection when using @Singular. What should happen if a null ref is passed? default: throw NullPointerException.") {}; // ##### Standalones ##### @@ -312,10 +325,14 @@ public class ConfigurationKeys { // ----- NonNull ----- /** - * lombok configuration: {@code lombok.nonNull.exceptionType} = one of: [{@code IllegalArgumentException}, {@code NullPointerException}, or {@code Assertion}]. + * lombok configuration: {@code lombok.nonNull.exceptionType} = one of: [{@code IllegalArgumentException}, {@code NullPointerException}, {@code JDK}, {@code Guava}, or {@code Assertion}]. * - * Sets the exception to throw if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}. If the chosen configuration is {@code Assertion}, an assertion is generated instead, - * which would mean your code throws an {@code AssertionError} if assertions are enabled, and does nothing if assertions are not enabled. + * Sets the behavior of the generated nullcheck if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}.<ul> + * <li>If the chosen configuration is {@code NullPointerException} (the default), or {@code IllegalArgumentException}, that exception type is a thrown, with as message <code><em>field-name</em> is marked non-null but is null</code>.</li> + * <li>If the chosen configuration is {@code Assert}, then an {@code assert} statement is generated. This means an {@code AssertionError} will be thrown if assertions are on (VM started with {@code -ea} parameter), and nothing happens if not.</li> + * <li>If the chosen configuration is {@code JDK}, a call to {@code java.util.Objects.requireNonNull} is generated with the fieldname passed along (which throws {@code NullPointerException}).</li> + * <li>If the chosen configuration is {@code Guava}, a call to {@code com.google.common.base.Preconditions.checkNotNull} is generated with the fieldname passed along (which throws {@code NullPointerException}).</li> + * </ul> */ public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).") {}; diff --git a/src/core/lombok/Singular.java b/src/core/lombok/Singular.java index 67edab96..f8cf6853 100644 --- a/src/core/lombok/Singular.java +++ b/src/core/lombok/Singular.java @@ -27,6 +27,10 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import lombok.core.LombokImmutableList; +import lombok.core.configuration.ExampleValueString; +import lombok.core.configuration.NullCheckExceptionType; + /** * The singular annotation is used together with {@code @Builder} to create single element 'add' methods in the builder for collections. */ @@ -35,4 +39,64 @@ import java.lang.annotation.Target; public @interface Singular { /** @return The singular name of this field. If it's a normal english plural, lombok will figure it out automatically. Otherwise, this parameter is mandatory. */ String value() default ""; + + NullCollectionBehavior nullBehavior() default NullCollectionBehavior.NULL_POINTER_EXCEPTION; + + @ExampleValueString("[NullPointerException | IllegalArgumentException | JDK | Guava | Ignore]") + public enum NullCollectionBehavior { + ILLEGAL_ARGUMENT_EXCEPTION { + @Override public String getExceptionType() { + return NullCheckExceptionType.ILLEGAL_ARGUMENT_EXCEPTION.getExceptionType(); + } + + @Override public LombokImmutableList<String> getMethod() { + return NullCheckExceptionType.ILLEGAL_ARGUMENT_EXCEPTION.getMethod(); + } + }, + NULL_POINTER_EXCEPTION { + @Override public String getExceptionType() { + return NullCheckExceptionType.NULL_POINTER_EXCEPTION.getExceptionType(); + } + + @Override public LombokImmutableList<String> getMethod() { + return NullCheckExceptionType.NULL_POINTER_EXCEPTION.getMethod(); + } + }, + JDK { + @Override public String getExceptionType() { + return NullCheckExceptionType.JDK.getExceptionType(); + } + + @Override public LombokImmutableList<String> getMethod() { + return NullCheckExceptionType.JDK.getMethod(); + } + }, + GUAVA { + @Override public String getExceptionType() { + return NullCheckExceptionType.GUAVA.getExceptionType(); + } + + @Override public LombokImmutableList<String> getMethod() { + return NullCheckExceptionType.GUAVA.getMethod(); + } + }, + IGNORE { + @Override public String getExceptionType() { + return null; + } + + @Override public LombokImmutableList<String> getMethod() { + return null; + } + }; + + + public String toExceptionMessage(String fieldName) { + return fieldName + " cannot be null"; + } + + public abstract String getExceptionType(); + + public abstract LombokImmutableList<String> getMethod(); + } } diff --git a/src/core/lombok/core/configuration/NullCheckExceptionType.java b/src/core/lombok/core/configuration/NullCheckExceptionType.java index 3c9e325d..4632916c 100644 --- a/src/core/lombok/core/configuration/NullCheckExceptionType.java +++ b/src/core/lombok/core/configuration/NullCheckExceptionType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2019 The Project Lombok Authors. + * Copyright (C) 2014-2020 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 @@ -23,7 +23,7 @@ package lombok.core.configuration; import lombok.core.LombokImmutableList; -@ExampleValueString("[NullPointerException | IllegalArgumentException | Assertion | JDK | GUAVA]") +@ExampleValueString("[NullPointerException | IllegalArgumentException | Assertion | JDK | Guava]") public enum NullCheckExceptionType { ILLEGAL_ARGUMENT_EXCEPTION { @Override public String getExceptionType() { diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 38aada23..883b1a29 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2018 The Project Lombok Authors. + * Copyright (C) 2013-2020 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 @@ -451,11 +451,6 @@ public class HandlerUtil { return null; } - /* NB: 'notnull' is not part of the pattern because there are lots of @NotNull annotations out there that are crappily named and actually mean - something else, such as 'this field must not be null _when saved to the db_ but its perfectly okay to start out as such, and a no-args - constructor and the implied starts-out-as-null state that goes with it is in fact mandatory' which happens with javax.validation.constraints.NotNull. - Various problems with spring have also been reported. See issue #287, issue #271, and issue #43. */ - public static final String DEFAULT_EXCEPTION_FOR_NON_NULL = "java.lang.NullPointerException"; /** diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java index 9d79d75c..5fe4b958 100755 --- a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -33,25 +33,31 @@ import java.util.Map; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.IntLiteral; 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.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.Reference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.lookup.ClassScope; @@ -60,10 +66,12 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import lombok.AccessLevel; +import lombok.Singular.NullCollectionBehavior; import lombok.core.LombokImmutableList; import lombok.core.SpiLoadUtil; import lombok.core.TypeLibrary; import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseNode; public class EclipseSingularsRecipes { @@ -126,13 +134,14 @@ public class EclipseSingularsRecipes { private final List<TypeReference> typeArgs; private final String targetFqn; private final EclipseSingularizer singularizer; + private final NullCollectionBehavior nullCollectionBehavior; private final ASTNode source; - public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source) { - this(annotation, singularName, pluralName, typeArgs, targetFqn, singularizer, source, new char[0]); + public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source, NullCollectionBehavior nullCollectionBehavior) { + this(annotation, singularName, pluralName, typeArgs, targetFqn, singularizer, source, nullCollectionBehavior, new char[0]); } - public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source, char[] setterPrefix) { + public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source, NullCollectionBehavior nullCollectionBehavior, char[] setterPrefix) { this.annotation = annotation; this.singularName = singularName; this.pluralName = pluralName; @@ -140,6 +149,7 @@ public class EclipseSingularsRecipes { this.targetFqn = targetFqn; this.singularizer = singularizer; this.source = source; + this.nullCollectionBehavior = nullCollectionBehavior; this.setterPrefix = setterPrefix; } @@ -187,6 +197,10 @@ public class EclipseSingularsRecipes { return singularizer; } + public NullCollectionBehavior getNullCollectionBehavior() { + return nullCollectionBehavior; + } + public String getTargetSimpleType() { int idx = targetFqn.lastIndexOf("."); return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); @@ -423,6 +437,45 @@ public class EclipseSingularsRecipes { } } + protected void nullBehaviorize(SingularData data, List<Statement> statements) { + NullCollectionBehavior behavior = data.getNullCollectionBehavior(); + + if (behavior == NullCollectionBehavior.IGNORE) { + Expression isNotNull = new EqualExpression(new SingleNameReference(data.getPluralName(), 0L), new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); + Block b = new Block(0); + b.statements = statements.toArray(new Statement[statements.size()]); + statements.clear(); + statements.add(new IfStatement(isNotNull, b, 0, 0)); + return; + } + + String exceptionTypeStr = behavior.getExceptionType(); + StringLiteral message = new StringLiteral(behavior.toExceptionMessage(new String(data.getPluralName())).toCharArray(), 0, 0, 0); + if (exceptionTypeStr != null) { + Expression isNull = new EqualExpression(new SingleNameReference(data.getPluralName(), 0L), new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + int partCount = 1; + for (int i = 0; i < exceptionTypeStr.length(); i++) if (exceptionTypeStr.charAt(i) == '.') partCount++; + long[] ps = new long[partCount]; + Arrays.fill(ps, 0L); + AllocationExpression alloc = new AllocationExpression(); + alloc.type = new QualifiedTypeReference(Eclipse.fromQualifiedName(exceptionTypeStr), ps); + alloc.arguments = new Expression[] {message}; + Statement t = new ThrowStatement(alloc, 0, 0); + statements.add(0, new IfStatement(isNull, t, 0, 0)); + return; + } + + MessageSend invoke = new MessageSend(); + LombokImmutableList<String> method = behavior.getMethod(); + char[][] utilityTypeName = new char[method.size() - 1][]; + for (int i = 0; i < method.size() - 1; i++) utilityTypeName[i] = method.get(i).toCharArray(); + + invoke.receiver = new QualifiedNameReference(utilityTypeName, new long[method.size() - 1], 0, 0); + invoke.selector = method.get(method.size() - 1).toCharArray(); + invoke.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L), message}; + statements.add(0, invoke); + } + protected abstract char[][] getEmptyMakerReceiver(String targetFqn); protected abstract char[] getEmptyMakerSelector(String targetFqn); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index bb031ebc..578fa2a3 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -79,6 +79,7 @@ import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; import lombok.Builder; import lombok.Builder.ObtainVia; +import lombok.Singular.NullCollectionBehavior; import lombok.ConfigurationKeys; import lombok.Singular; import lombok.ToString; @@ -992,7 +993,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (!annotationTypeMatches(Singular.class, child)) continue; char[] pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((AbstractVariableDeclaration) node.get()).name; AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); - String explicitSingular = ann.getInstance().value(); + Singular singularInstance = ann.getInstance(); + String explicitSingular = singularInstance.value(); if (explicitSingular.isEmpty()) { if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); @@ -1034,9 +1036,21 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return null; } - return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, setterPrefix.toCharArray()); + NullCollectionBehavior behavior = getNullBehaviorFor(ann, singularInstance, node); + return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, behavior, setterPrefix.toCharArray()); } return null; } + + static NullCollectionBehavior getNullBehaviorFor(AnnotationValues<Singular> ann, Singular singularInstance, EclipseNode node) { + NullCollectionBehavior behavior; + if (ann.isExplicit("nullBehavior")) { + behavior = singularInstance.nullBehavior(); + } else { + behavior = node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_NULL_COLLECTIONS); + } + if (behavior == null) return NullCollectionBehavior.NULL_POINTER_EXCEPTION; + return behavior; + } } diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 3a2e59f7..4b450a07 100755 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -79,6 +79,7 @@ import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; import lombok.Builder; import lombok.Builder.ObtainVia; +import lombok.Singular.NullCollectionBehavior; import lombok.ConfigurationKeys; import lombok.Singular; import lombok.ToString; @@ -1000,7 +1001,8 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { char[] pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((AbstractVariableDeclaration) node.get()).name; AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); - String explicitSingular = ann.getInstance().value(); + Singular singularInstance = ann.getInstance(); + String explicitSingular = singularInstance.value(); if (explicitSingular.isEmpty()) { if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); @@ -1042,7 +1044,8 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { return null; } - return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source); + NullCollectionBehavior behavior = HandleBuilder.getNullBehaviorFor(ann, singularInstance, node); + return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, behavior); } return null; diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java index 8f80d228..b067ad80 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2019 The Project Lombok Authors. + * Copyright (C) 2015-2020 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 @@ -195,6 +195,9 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { thisDotFieldDotAddAll.receiver = thisDotField; thisDotFieldDotAddAll.selector = (getAddMethodName() + "All").toCharArray(); statements.add(thisDotFieldDotAddAll); + + nullBehaviorize(data, statements); + if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java index e3a99008..ba447397 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2019 The Project Lombok Authors. + * Copyright (C) 2015-2020 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 @@ -173,6 +173,8 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula thisDotFieldDotAddAll.receiver = thisDotField; thisDotFieldDotAddAll.selector = "addAll".toCharArray(); statements.add(thisDotFieldDotAddAll); + + nullBehaviorize(data, statements); if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java index b0223c50..e91c6616 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2019 The Project Lombok Authors. + * Copyright (C) 2015-2020 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 @@ -305,6 +305,9 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer forEachContent.statements = new Statement[] {addKey, addValue}; forEach.action = forEachContent; statements.add(forEach); + + nullBehaviorize(data, statements); + if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 5fc3957b..75f3de2c 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -61,6 +61,7 @@ import lombok.Builder; import lombok.Builder.ObtainVia; import lombok.ConfigurationKeys; import lombok.Singular; +import lombok.Singular.NullCollectionBehavior; import lombok.ToString; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; @@ -869,8 +870,9 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (!annotationTypeMatches(Singular.class, child)) continue; Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name; AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + Singular singularInstance = ann.getInstance(); deleteAnnotationIfNeccessary(child, Singular.class); - String explicitSingular = ann.getInstance().value(); + String explicitSingular = singularInstance.value(); if (explicitSingular.isEmpty()) { if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); @@ -906,9 +908,22 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return null; } - return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer, setterPrefix); + NullCollectionBehavior behavior = getNullBehaviorFor(ann, singularInstance, node); + + return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer, behavior, setterPrefix); } return null; } + + static NullCollectionBehavior getNullBehaviorFor(AnnotationValues<Singular> ann, Singular singularInstance, JavacNode node) { + NullCollectionBehavior behavior; + if (ann.isExplicit("nullBehavior")) { + behavior = singularInstance.nullBehavior(); + } else { + behavior = node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_NULL_COLLECTIONS); + } + if (behavior == null) return NullCollectionBehavior.NULL_POINTER_EXCEPTION; + return behavior; + } } diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index b3002946..692ee60b 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -59,6 +59,7 @@ import com.sun.tools.javac.util.Name; import lombok.AccessLevel; import lombok.Builder; import lombok.Builder.ObtainVia; +import lombok.Singular.NullCollectionBehavior; import lombok.ConfigurationKeys; import lombok.Singular; import lombok.ToString; @@ -939,8 +940,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> { if (!annotationTypeMatches(Singular.class, child)) continue; Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name; AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + Singular singularInstance = ann.getInstance(); deleteAnnotationIfNeccessary(child, Singular.class); - String explicitSingular = ann.getInstance().value(); + String explicitSingular = singularInstance.value(); if (explicitSingular.isEmpty()) { if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); @@ -974,7 +976,8 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> { return null; } - return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer); + NullCollectionBehavior behavior = HandleBuilder.getNullBehaviorFor(ann, singularInstance, node); + return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer, behavior); } return null; diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java index c1e6edd8..a5d4a295 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -24,33 +24,24 @@ package lombok.javac.handlers; import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; -import com.sun.tools.javac.tree.JCTree.JCBlock; -import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import lombok.AccessLevel; -import lombok.ConfigurationKeys; -import lombok.core.LombokImmutableList; -import lombok.core.SpiLoadUtil; -import lombok.core.TypeLibrary; -import lombok.core.configuration.CheckerFrameworkVersion; -import lombok.core.handlers.HandlerUtil; -import lombok.javac.JavacNode; -import lombok.javac.JavacTreeMaker; - import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.util.Context; @@ -58,6 +49,17 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; +import lombok.AccessLevel; +import lombok.ConfigurationKeys; +import lombok.Singular.NullCollectionBehavior; +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + public class JavacSingularsRecipes { public interface ExpressionMaker { JCExpression make(); @@ -120,12 +122,13 @@ public class JavacSingularsRecipes { private final String targetFqn; private final JavacSingularizer singularizer; private final String setterPrefix; + private final NullCollectionBehavior nullCollectionBehavior; - public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer) { - this(annotation, singularName, pluralName, typeArgs, targetFqn, singularizer, ""); + public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer, NullCollectionBehavior nullCollectionBehavior) { + this(annotation, singularName, pluralName, typeArgs, targetFqn, singularizer, nullCollectionBehavior, ""); } - public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer, String setterPrefix) { + public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer, NullCollectionBehavior nullCollectionBehavior, String setterPrefix) { this.annotation = annotation; this.singularName = singularName; this.pluralName = pluralName; @@ -133,6 +136,7 @@ public class JavacSingularsRecipes { this.targetFqn = targetFqn; this.singularizer = singularizer; this.setterPrefix = setterPrefix; + this.nullCollectionBehavior = nullCollectionBehavior; } public JavacNode getAnnotation() { @@ -163,6 +167,10 @@ public class JavacSingularsRecipes { return singularizer; } + public NullCollectionBehavior getNullCollectionBehavior() { + return nullCollectionBehavior; + } + public String getTargetSimpleType() { int idx = targetFqn.lastIndexOf("."); return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); @@ -320,6 +328,7 @@ public class JavacSingularsRecipes { private void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent, AccessLevel access) { ListBuffer<JCStatement> statements = generatePluralMethodStatements(maker, data, builderType, source); + Name name = data.getPluralName(); String setterPrefix = data.getSetterPrefix(); if (setterPrefix.isEmpty() && !fluent) setterPrefix = getAddMethodName() + "All"; @@ -329,13 +338,40 @@ public class JavacSingularsRecipes { long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); statements.prepend(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); + + NullCollectionBehavior behavior = data.getNullCollectionBehavior(); + + if (behavior == NullCollectionBehavior.IGNORE) { + JCExpression incomingIsNotNull = maker.Binary(CTC_NOT_EQUAL, maker.Ident(data.getPluralName()), maker.Literal(CTC_BOT, null)); + JCStatement onNotNull = maker.Block(0, statements.toList()); + statements = new ListBuffer<JCStatement>(); + statements.add(maker.If(incomingIsNotNull, onNotNull, null)); + } else { + JCLiteral message = maker.Literal(behavior.toExceptionMessage(data.getPluralName().toString())); + if (behavior.getExceptionType() != null) { + JCExpression incomingIsNull = maker.Binary(CTC_EQUAL, maker.Ident(data.getPluralName()), maker.Literal(CTC_BOT, null)); + JCExpression exType = genTypeRef(builderType, behavior.getExceptionType()); + JCExpression exception = maker.NewClass(null, List.<JCExpression>nil(), exType, List.<JCExpression>of(message), null); + JCStatement onNull = maker.Throw(exception); + statements.prepend(maker.If(incomingIsNull, onNull, null)); + } else { + LombokImmutableList<String> method = behavior.getMethod(); + JCExpression invoke = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, method), List.of(maker.Ident(data.getPluralName()), message)); + statements.prepend(maker.Exec(invoke)); + } + } + finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, List.of(param), access); } protected ListBuffer<JCStatement> generatePluralMethodStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), getAddMethodName() + "All"); JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getPluralName()))); - return new ListBuffer<JCStatement>().append(maker.Exec(invokeAdd)); + statements.append(maker.Exec(invokeAdd)); + + return statements; } protected abstract JCExpression getPluralMethodParamType(JavacNode builderType); |