From e95680a76733c22ee5937a586ee50c703d5ba621 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 20 Jan 2020 15:25:08 +0100 Subject: [issue #2221] [issue #788] Lombok now adds nullity annotations. Which 'flavour' is defined in lombok.config; applied to toString, equals, canEqual, and plural-form of `@Singular`. --- src/core/lombok/ConfigurationKeys.java | 27 ++++ .../core/configuration/NullAnnotationLibrary.java | 157 +++++++++++++++++++++ src/core/lombok/core/handlers/HandlerUtil.java | 14 +- .../eclipse/handlers/EclipseHandlerUtil.java | 97 +++++++++++++ .../eclipse/handlers/EclipseSingularsRecipes.java | 6 +- .../eclipse/handlers/HandleEqualsAndHashCode.java | 2 + .../lombok/eclipse/handlers/HandleToString.java | 3 +- src/core/lombok/eclipse/handlers/HandleWith.java | 4 +- .../singulars/EclipseGuavaSingularizer.java | 12 +- .../EclipseJavaUtilListSetSingularizer.java | 9 +- .../singulars/EclipseJavaUtilMapSingularizer.java | 9 +- .../javac/handlers/HandleEqualsAndHashCode.java | 21 +-- src/core/lombok/javac/handlers/HandleToString.java | 8 +- src/core/lombok/javac/handlers/HandleWith.java | 3 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 99 +++++++++++++ .../javac/handlers/JavacSingularsRecipes.java | 22 ++- .../handlers/singulars/JavacGuavaSingularizer.java | 11 +- 17 files changed, 450 insertions(+), 54 deletions(-) create mode 100644 src/core/lombok/core/configuration/NullAnnotationLibrary.java (limited to 'src') diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 691346cd..559fff92 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -30,6 +30,7 @@ import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; import lombok.core.configuration.IdentifierName; import lombok.core.configuration.LogDeclaration; +import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.configuration.TypeName; @@ -89,6 +90,32 @@ public class ConfigurationKeys { */ public static final ConfigurationKey ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS = new ConfigurationKey("lombok.extern.findbugs.addSuppressFBWarnings", "Generate @edu.umd.cs.findbugs.annotations.SuppressFBWarnings on all generated code (default: false).") {}; + /** + * lombok configuration: {@code lombok.addNullAnnotations = one of: [{@code none}, {@code javax}, {@code eclipse}, {@code jetbrains}, {@code netbeans}, {@code androidx}, {@code android.support}, {@code checkerframework}, {@code findbugs}, {@code spring}, {@code JML}, or a custom set of fully qualified annotation types]. + * + * Lombok generally copies relevant nullity annotations from your source code to the right places. However, sometimes lombok generates code where the nullability of some node is not dependent on something in your source code. You can configure lombok to add an appropriate nullity annotation in this case.
    + *
  • {@code none} (the default) - no annotations are added.
  • + *
  • {@code javax} - The annotations {@code javax.annotation.NonNull} and {@code javax.annotation.Nullable} are used.
  • + *
  • {@code eclipse} - The annotations {@code org.eclipse.jdt.annotation.NonNull} and {@code org.eclipse.jdt.annotation.Nullable} are used.
  • + *
  • {@code jetbrains} - The annotations {@code org.jetbrains.annotations.NotNull} and {@code org.jetbrains.annotations.Nullable} are used.
  • + *
  • {@code netbeans} - The annotations {@code org.netbeans.api.annotations.common.NonNull} and {@code org.netbeans.api.annotations.common.NullAllowed} are used.
  • + *
  • {@code androidx} - The annotations {@code androidx.annotation.NonNull} and {@code androidx.annotation.Nullable} are used.
  • + *
  • {@code android.support} - The annotations {@code android.support.annotation.NonNull} and {@code android.support.annotation.Nullable} are used.
  • + *
  • {@code checkerframework} - The annotations {@code org.checkerframework.checker.nullness.qual.NonNull} and {@code org.checkerframework.checker.nullness.qual.Nullable} are used.
  • + *
  • {@code findbugs} - The annotations {@code edu.umd.cs.findbugs.annotations.NonNull} and {@code edu.umd.cs.findbugs.annotations.Nullable} are used.
  • + *
  • {@code spring} - The annotations {@code org.springframework.lang.NonNull} and {@code org.springframework.lang.Nullable} are used.
  • + *
  • {@code jml} - The annotations {@code org.jmlspecs.annotation.NonNull} and {@code org.jmlspecs.annotation.Nullable} are used.
  • + *
  • CUSTOM:fully.qualified.nonnull.annotation:fully.qualified.nullable.annotation to configure your own types; the nullable annotation (and the colon) are optional.
  • + *
+ *

+ * Lombok will not put these annotations on the classpath for you; your project must be set up such that these annotations are available. + *

+ * Current features which use this configuration:

    + *
  • {@code @Builder.Singular} makes methods that accept a collection, all of which must be added. The parameter to this 'plural form' method is annotated.
  • + *
+ */ + public static final ConfigurationKey ADD_NULL_ANNOTATIONS = new ConfigurationKey("lombok.addNullAnnotations", "Generate some style of null annotation for generated code where this is relevant. (default: none).") {}; + // ----- *ArgsConstructor ----- /** diff --git a/src/core/lombok/core/configuration/NullAnnotationLibrary.java b/src/core/lombok/core/configuration/NullAnnotationLibrary.java new file mode 100644 index 00000000..68760e6f --- /dev/null +++ b/src/core/lombok/core/configuration/NullAnnotationLibrary.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class NullAnnotationLibrary implements ConfigurationValueType { + private final String key; + private final String nonNullAnnotation; + private final String nullableAnnotation; + private final boolean typeUse; + + private NullAnnotationLibrary(String key, String nonNullAnnotation, String nullableAnnotation, boolean typeUse) { + this.key = key; + this.nonNullAnnotation = nonNullAnnotation; + this.nullableAnnotation = nullableAnnotation; + this.typeUse = typeUse; + } + + /** + * Returns the fully qualified annotation name to apply to non-null elements. If {@code null} is returned, apply no annotation. + */ + public String getNonNullAnnotation() { + return nonNullAnnotation; + } + + /** + * Returns the fully qualified annotation name to apply to nullable elements. If {@code null} is returned, apply no annotation. + */ + public String getNullableAnnotation() { + return nullableAnnotation; + } + + /** + * If {@code true}, the annotation can only be used in TYPE_USE form, otherwise, prefer to annotate the parameter, not the type of the parameter (or the method, not the return type, etc). + */ + public boolean isTypeUse() { + return typeUse; + } + + public static final NullAnnotationLibrary NONE = new NullAnnotationLibrary("none", null, null, false); + public static final NullAnnotationLibrary JAVAX = new NullAnnotationLibrary("javax", "javax.annotation.Nonnull", "javax.annotation.Nullable", false); + public static final NullAnnotationLibrary ECLIPSE = new NullAnnotationLibrary("eclipse", "org.eclipse.jdt.annotation.NonNull", "org.eclipse.jdt.annotation.Nullable", true); + public static final NullAnnotationLibrary JETBRAINS = new NullAnnotationLibrary("jetbrains", "org.jetbrains.annotations.NotNull", "org.jetbrains.annotations.Nullable", false); + public static final NullAnnotationLibrary NETBEANS = new NullAnnotationLibrary("netbeans", "org.netbeans.api.annotations.common.NonNull", "org.netbeans.api.annotations.common.NullAllowed", false); + public static final NullAnnotationLibrary ANDROIDX = new NullAnnotationLibrary("androidx", "androidx.annotation.NonNull", "androidx.annotation.Nullable", false); + public static final NullAnnotationLibrary ANDROID_SUPPORT = new NullAnnotationLibrary("android.support", "android.support.annotation.NonNull", "android.support.annotation.Nullable", false); + public static final NullAnnotationLibrary CHECKERFRAMEWORK = new NullAnnotationLibrary("checkerframework", "org.checkerframework.checker.nullness.qual.NonNull", "org.checkerframework.checker.nullness.qual.Nullable", true); + public static final NullAnnotationLibrary FINDBUGS = new NullAnnotationLibrary("findbugs", "edu.umd.cs.findbugs.annotations.NonNull", "edu.umd.cs.findbugs.annotations.Nullable", false); + public static final NullAnnotationLibrary SPRING = new NullAnnotationLibrary("spring", "org.springframework.lang.NonNull", "org.springframework.lang.Nullable", false); + public static final NullAnnotationLibrary JML = new NullAnnotationLibrary("jml", "org.jmlspecs.annotation.NonNull", "org.jmlspecs.annotation.Nullable", false); + + private static final List ALL_AVAILABLE; + private static final String EXAMPLE_VALUE; + + static { + ArrayList out = new ArrayList(); + StringBuilder example = new StringBuilder(); + for (Field f : NullAnnotationLibrary.class.getDeclaredFields()) { + if (f.getType() != NullAnnotationLibrary.class || !Modifier.isStatic(f.getModifiers()) || !Modifier.isPublic(f.getModifiers())) continue; + try { + NullAnnotationLibrary nal = (NullAnnotationLibrary) f.get(null); + out.add(nal); + example.append(nal.key).append(" | "); + } catch (IllegalAccessException e) { + continue; + } + } + out.trimToSize(); + example.append("CUSTOM:com.foo.my.nonnull.annotation:com.foo.my.nullable.annotation"); + ALL_AVAILABLE = Collections.unmodifiableList(out); + EXAMPLE_VALUE = example.toString(); + } + + public static NullAnnotationLibrary custom(String nonNullAnnotation, String nullableAnnotation, boolean typeUse) { + if (nonNullAnnotation == null && nullableAnnotation == null) return NONE; + String typeUseStr = typeUse ? "TYPE_USE:" : ""; + if (nullableAnnotation == null) return new NullAnnotationLibrary("custom:" + typeUseStr + nonNullAnnotation, nonNullAnnotation, null, typeUse); + if (nonNullAnnotation == null) return new NullAnnotationLibrary("custom::" + typeUseStr + nullableAnnotation, null, nullableAnnotation, typeUse); + return new NullAnnotationLibrary("custom:" + typeUseStr + nonNullAnnotation + ":" + nullableAnnotation, nonNullAnnotation, nullableAnnotation, typeUse); + } + + public static String description() { + return "nullity-annotation-library"; + } + + public static String exampleValue() { + return EXAMPLE_VALUE; + } + + public static NullAnnotationLibrary valueOf(String in) { + String ci = in == null ? "" : in.toLowerCase(); + if (ci.length() == 0) return NONE; + for (NullAnnotationLibrary nal : ALL_AVAILABLE) if (nal.key.equals(ci)) return nal; + if (!ci.startsWith("custom:")) { + StringBuilder out = new StringBuilder("Invalid null annotation library. Valid options: "); + for (NullAnnotationLibrary nal : ALL_AVAILABLE) out.append(nal.key).append(", "); + out.setLength(out.length() - 2); + out.append(" or CUSTOM:[TYPE_USE:]nonnull.annotation.type:nullable.annotation.type"); + throw new IllegalArgumentException(out.toString()); + } + boolean typeUse = ci.startsWith("custom:type_use:"); + int start = typeUse ? 16 : 7; + int split = ci.indexOf(':', start); + if (split == -1) { + String nonNullAnnotation = ci.substring(start); + return custom(verifyTypeName(nonNullAnnotation), null, typeUse); + } + String nonNullAnnotation = ci.substring(start, split); + String nullableAnnotation = ci.substring(split + 1); + return custom(verifyTypeName(nonNullAnnotation), verifyTypeName(nullableAnnotation), typeUse); + } + + private static final String MSG = "Not an identifier (provide a fully qualified type for custom: nullity annotations): "; + private static String verifyTypeName(String fqn) { + boolean atStart = true; + for (int i = 0; i < fqn.length(); i++) { + char c = fqn.charAt(i); + if (Character.isJavaIdentifierStart(c)) { + atStart = false; + continue; + } + if (atStart) throw new IllegalArgumentException(MSG + fqn); + if (c == '.') { + atStart = true; + continue; + } + if (Character.isJavaIdentifierPart(c)) continue; + throw new IllegalArgumentException(MSG + fqn); + } + if (atStart) throw new IllegalArgumentException(MSG + fqn); + return fqn; + } +} diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 883b1a29..bfc7b690 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -79,7 +79,7 @@ public class HandlerUtil { public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS; static { NONNULL_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { - "android.annotation.NonNull", + "androidx.annotation.NonNull", "android.support.annotation.NonNull", "com.sun.istack.internal.NotNull", "edu.umd.cs.findbugs.annotations.NonNull", @@ -95,6 +95,8 @@ public class HandlerUtil { "org.springframework.lang.NonNull", })); BASE_COPYABLE_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { + "androidx.annotation.NonNull", + "androidx.annotation.Nullable", "android.support.annotation.NonNull", "android.support.annotation.Nullable", "edu.umd.cs.findbugs.annotations.NonNull", @@ -104,6 +106,8 @@ public class HandlerUtil { "javax.annotation.Nonnull", "javax.annotation.Nullable", "lombok.NonNull", + "org.jmlspecs.annotation.NonNull", + "org.jmlspecs.annotation.Nullable", // To update Checker Framework annotations, run: // grep --recursive --files-with-matches -e '^@Target\b.*TYPE_USE' $CHECKERFRAMEWORK/checker/src/main/java $CHECKERFRAMEWORK/framework/src/main/java | grep '\.java$' | sed 's/.*\/java\//\t\t\t"/' | sed 's/\.java$/",/' | sed 's/\//./g' | sort // Only add new annotations, do not remove annotations that have been removed from the lastest version of the Checker Framework. @@ -304,11 +308,13 @@ public class HandlerUtil { "org.jetbrains.annotations.Nullable", "org.springframework.lang.NonNull", "org.springframework.lang.Nullable", + "org.netbeans.api.annotations.common.NonNull", + "org.netbeans.api.annotations.common.NullAllowed", })); COPY_TO_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { - "com.fasterxml.jackson.annotation.JsonProperty", - "com.fasterxml.jackson.annotation.JsonSetter", - })); + "com.fasterxml.jackson.annotation.JsonProperty", + "com.fasterxml.jackson.annotation.JsonSetter", + })); } /** Checks if the given name is a valid identifier. diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index aa48e000..bd0ad23b 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -43,6 +43,7 @@ import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; @@ -112,6 +113,7 @@ import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.LombokImmutableList; import lombok.core.TypeResolver; import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.configuration.TypeName; import lombok.core.debug.ProblemReporter; @@ -2381,6 +2383,101 @@ public class EclipseHandlerUtil { return p.equals("Object") || p.equals("java.lang.Object"); } + public static void createRelevantNullableAnnotation(EclipseNode typeNode, MethodDeclaration mth) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToMethodDecl(typeNode, mth, lib.getNullableAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNonNullAnnotation(EclipseNode typeNode, MethodDeclaration mth) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToMethodDecl(typeNode, mth, lib.getNonNullAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNullableAnnotation(EclipseNode typeNode, Argument arg) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToVarDecl(typeNode, arg, lib.getNullableAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNonNullAnnotation(EclipseNode typeNode, Argument arg) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToVarDecl(typeNode, arg, lib.getNonNullAnnotation(), lib.isTypeUse()); + } + + private static void applyAnnotationToMethodDecl(EclipseNode typeNode, MethodDeclaration mth, String annType, boolean typeUse) { + if (annType == null) return; + + int partCount = 1; + for (int i = 0; i < annType.length(); i++) if (annType.charAt(i) == '.') partCount++; + long[] ps = new long[partCount]; + Arrays.fill(ps, 0L); + Annotation ann = new MarkerAnnotation(new QualifiedTypeReference(Eclipse.fromQualifiedName(annType), ps), 0); + + if (!typeUse || mth.returnType == null || mth.returnType.getTypeName().length < 2) { + Annotation[] a = mth.annotations; + if (a == null) a = new Annotation[1]; + else { + Annotation[] b = new Annotation[a.length + 1]; + System.arraycopy(a, 0, b, 0, a.length); + a = b; + } + a[a.length - 1] = ann; + mth.annotations = a; + } else { + int len = mth.returnType.getTypeName().length; + if (mth.returnType.annotations == null) mth.returnType.annotations = new Annotation[len][]; + Annotation[] a = mth.returnType.annotations[len - 1]; + if (a == null) a = new Annotation[1]; + else { + Annotation[] b = new Annotation[a.length + 1]; + System.arraycopy(a, 0, b, 1, a.length); + a = b; + } + a[0] = ann; + mth.returnType.annotations[len - 1] = a; + } + } + private static void applyAnnotationToVarDecl(EclipseNode typeNode, Argument arg, String annType, boolean typeUse) { + if (annType == null) return; + + int partCount = 1; + for (int i = 0; i < annType.length(); i++) if (annType.charAt(i) == '.') partCount++; + long[] ps = new long[partCount]; + Arrays.fill(ps, 0L); + Annotation ann = new MarkerAnnotation(new QualifiedTypeReference(Eclipse.fromQualifiedName(annType), ps), 0); + + if (!typeUse || arg.type.getTypeName().length < 2) { + Annotation[] a = arg.annotations; + if (a == null) a = new Annotation[1]; + else { + Annotation[] b = new Annotation[a.length + 1]; + System.arraycopy(a, 0, b, 0, a.length); + a = b; + } + a[a.length - 1] = ann; + arg.annotations = a; + } else { + int len = arg.type.getTypeName().length; + if (arg.type.annotations == null) arg.type.annotations = new Annotation[len][]; + Annotation[] a = arg.type.annotations[len - 1]; + if (a == null) a = new Annotation[1]; + else { + Annotation[] b = new Annotation[a.length + 1]; + System.arraycopy(a, 0, b, 1, a.length); + a = b; + } + a[0] = ann; + arg.type.annotations[len - 1] = a; + } + } + public static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java index 5fe4b958..cbbd4cc8 100755 --- a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -35,6 +35,7 @@ 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.Argument; import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; @@ -437,7 +438,7 @@ public class EclipseSingularsRecipes { } } - protected void nullBehaviorize(SingularData data, List statements) { + protected void nullBehaviorize(EclipseNode typeNode, SingularData data, List statements, Argument arg) { NullCollectionBehavior behavior = data.getNullCollectionBehavior(); if (behavior == NullCollectionBehavior.IGNORE) { @@ -446,9 +447,12 @@ public class EclipseSingularsRecipes { b.statements = statements.toArray(new Statement[statements.size()]); statements.clear(); statements.add(new IfStatement(isNotNull, b, 0, 0)); + EclipseHandlerUtil.createRelevantNullableAnnotation(typeNode, arg); return; } + EclipseHandlerUtil.createRelevantNonNullAnnotation(typeNode, arg); + String exceptionTypeStr = behavior.getExceptionType(); StringLiteral message = new StringLiteral(behavior.toExceptionMessage(new String(data.getPluralName())).toCharArray(), 0, 0, 0); if (exceptionTypeStr != null) { diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 46474b07..959b2cae 100755 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -557,6 +557,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler statements = new ArrayList(); @@ -806,6 +807,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler { method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; method.statements = new Statement[] { returnStatement }; + EclipseHandlerUtil.createRelevantNonNullAnnotation(type, method); return method; } diff --git a/src/core/lombok/eclipse/handlers/HandleWith.java b/src/core/lombok/eclipse/handlers/HandleWith.java index 8c8c3712..4771818d 100644 --- a/src/core/lombok/eclipse/handlers/HandleWith.java +++ b/src/core/lombok/eclipse/handlers/HandleWith.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2019 The Project Lombok Authors. + * Copyright (C) 2012-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 @@ -287,6 +287,8 @@ public class HandleWith extends EclipseAnnotationHandler { } param.annotations = copyAnnotations(source, copyableAnnotations, onParam.toArray(new Annotation[0])); + EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method); + method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java index b067ad80..5d656c91 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -196,16 +196,18 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { thisDotFieldDotAddAll.selector = (getAddMethodName() + "All").toCharArray(); statements.add(thisDotFieldDotAddAll); - nullBehaviorize(data, statements); + TypeReference paramType; + paramType = new QualifiedTypeReference(fromQualifiedName(getAddAllTypeName()), NULL_POSS); + paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); + + nullBehaviorize(builderType, data, statements, param); if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); - TypeReference paramType; - paramType = new QualifiedTypeReference(fromQualifiedName(getAddAllTypeName()), NULL_POSS); - paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs()); - Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); + md.arguments = new Argument[] {param}; md.returnType = returnType; char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray(); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java index ba447397..024f5880 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -174,14 +174,15 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula thisDotFieldDotAddAll.selector = "addAll".toCharArray(); statements.add(thisDotFieldDotAddAll); - nullBehaviorize(data, statements); + TypeReference paramType = new QualifiedTypeReference(TypeConstants.JAVA_UTIL_COLLECTION, NULL_POSS); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); + + nullBehaviorize(builderType, data, statements, param); if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); - TypeReference paramType = new QualifiedTypeReference(TypeConstants.JAVA_UTIL_COLLECTION, NULL_POSS); - paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); - Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); md.arguments = new Argument[] {param}; md.returnType = returnType; char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray(); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java index e91c6616..843fd073 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -306,15 +306,16 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer forEach.action = forEachContent; statements.add(forEach); - nullBehaviorize(data, statements); + TypeReference paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); + + nullBehaviorize(builderType, data, statements, param); if (returnStatement != null) statements.add(returnStatement); md.statements = statements.toArray(new Statement[0]); - TypeReference paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); - paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); - Argument param = new Argument(data.getPluralName(), 0, paramType, ClassFileConstants.AccFinal); md.arguments = new Argument[] {param}; md.returnType = returnType; diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 0d0369b9..c4cf28da 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -383,18 +383,6 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler annsOnParamOnMethod = List.nil(); - String nearest = scanForNearestAnnotation(typeNode, "org.eclipse.jdt.annotation.NonNullByDefault"); - if (nearest != null) { - JCAnnotation m = maker.Annotation(genTypeRef(typeNode, "org.eclipse.jdt.annotation.Nullable"), List.nil()); - annsOnParamOnMethod = annsOnParamOnMethod.prepend(m); - } - - nearest = scanForNearestAnnotation(typeNode, "javax.annotation.ParametersAreNullableByDefault", "javax.annotation.ParametersAreNonnullByDefault"); - if ("javax.annotation.ParametersAreNonnullByDefault".equals(nearest)) { - JCAnnotation m = maker.Annotation(genTypeRef(typeNode, "javax.annotation.Nullable"), List.nil()); - annsOnParamOnMethod = annsOnParamOnMethod.prepend(m); - } - JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.nil()); List annsOnMethod = List.of(overrideAnnotation); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(typeNode); @@ -415,7 +403,10 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler statements = new ListBuffer(); - final List params = List.of(maker.VarDef(maker.Modifiers(finalFlag | Flags.PARAMETER, onParam), oName, objectType, null)); + JCVariableDecl param = maker.VarDef(maker.Modifiers(finalFlag | Flags.PARAMETER, onParam), oName, objectType, null); + JavacHandlerUtil.createRelevantNullableAnnotation(typeNode, param); + + final List params = List.of(param); /* if (o == this) return true; */ { statements.append(maker.If(maker.Binary(CTC_EQUAL, maker.Ident(oName), @@ -538,7 +529,9 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler params = List.of(maker.VarDef(maker.Modifiers(flags, onParam), otherName, objectType, null)); + JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, onParam), otherName, objectType, null); + createRelevantNullableAnnotation(typeNode, param); + List params = List.of(param); JCBlock body = maker.Block(0, List.of( maker.Return(maker.TypeTest(maker.Ident(otherName), createTypeReference(typeNode, false))))); diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index d0d36e06..0a950f7c 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -253,8 +253,10 @@ public class HandleToString extends JavacAnnotationHandler { JCBlock body = maker.Block(0, List.of(returnStatement)); - return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("toString"), returnType, - List.nil(), List.nil(), List.nil(), body, null), source, typeNode.getContext()); + JCMethodDecl methodDef = maker.MethodDef(mods, typeNode.toName("toString"), returnType, + List.nil(), List.nil(), List.nil(), body, null); + createRelevantNonNullAnnotation(typeNode, methodDef); + return recursiveSetGeneratedBy(methodDef, source, typeNode.getContext()); } public static String getTypeName(JavacNode typeNode) { diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java index 7b2417da..6977b10e 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2019 The Project Lombok Authors. + * Copyright (C) 2012-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 @@ -211,6 +211,7 @@ public class HandleWith extends JavacAnnotationHandler { long access = toJavacModifier(level); JCMethodDecl createdWith = createWith(access, fieldNode, fieldNode.getTreeMaker(), source, onMethod, onParam, makeAbstract); + createRelevantNonNullAnnotation(fieldNode, createdWith); ClassSymbol sym = ((JCClassDecl) fieldNode.up().get()).sym; Type returnType = sym == null ? null : sym.type; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 9359b1ae..0ef8c359 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -94,6 +94,7 @@ import lombok.core.CleanupTask; import lombok.core.LombokImmutableList; import lombok.core.TypeResolver; import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.configuration.TypeName; import lombok.core.handlers.HandlerUtil; @@ -1085,6 +1086,13 @@ public class JavacHandlerUtil { } } + static void setAnnotations(JCTree obj, List anns) { + init(obj.getClass()); + try { + ANNOTATIONS.set(obj, anns); + } catch (Exception e) {} + } + static JCExpression getUnderlyingType(JCTree obj) { init(obj.getClass()); try { @@ -2054,4 +2062,95 @@ public class JavacHandlerUtil { String p = extending.toString(); return p.equals("Object") || p.equals("java.lang.Object"); } + + public static void createRelevantNullableAnnotation(JavacNode typeNode, JCMethodDecl mth) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + applyAnnotationToMethodDecl(typeNode, mth, lib.getNullableAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCMethodDecl mth) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + applyAnnotationToMethodDecl(typeNode, mth, lib.getNonNullAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCVariableDecl arg) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToVarDecl(typeNode, arg, lib.getNonNullAnnotation(), lib.isTypeUse()); + } + + public static void createRelevantNullableAnnotation(JavacNode typeNode, JCVariableDecl arg) { + NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); + if (lib == null) return; + + applyAnnotationToVarDecl(typeNode, arg, lib.getNullableAnnotation(), lib.isTypeUse()); + } + + private static void applyAnnotationToMethodDecl(JavacNode typeNode, JCMethodDecl mth, String annType, boolean typeUse) { + if (annType == null) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil()); + if (typeUse) { + JCExpression resType = mth.restype; + if (resType instanceof JCTypeApply) { + JCTypeApply ta = (JCTypeApply) resType; + resType = ta.clazz; + } + + if (resType instanceof JCFieldAccess || resType instanceof JCArrayTypeTree) { + mth.restype = maker.AnnotatedType(List.of(m), resType); + return; + } + + if (JCAnnotatedTypeReflect.is(resType)) { + List annotations = JCAnnotatedTypeReflect.getAnnotations(resType); + JCAnnotatedTypeReflect.setAnnotations(resType, annotations.prepend(m)); + return; + } + + if (resType instanceof JCPrimitiveTypeTree || resType instanceof JCIdent) { + mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m); + } + } else { + mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m); + } + } + + private static void applyAnnotationToVarDecl(JavacNode typeNode, JCVariableDecl arg, String annType, boolean typeUse) { + if (annType == null) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil()); + if (typeUse) { + JCExpression varType = arg.vartype; + JCTypeApply ta = null; + if (varType instanceof JCTypeApply) { + ta = (JCTypeApply) varType; + varType = ta.clazz; + } + + if (varType instanceof JCFieldAccess || varType instanceof JCArrayTypeTree) { + varType = maker.AnnotatedType(List.of(m), varType); + if (ta != null) ta.clazz = varType; + else arg.vartype = varType; + return; + } + + if (JCAnnotatedTypeReflect.is(varType)) { + List annotations = JCAnnotatedTypeReflect.getAnnotations(varType); + JCAnnotatedTypeReflect.setAnnotations(varType, annotations.prepend(m)); + return; + } + + if (varType instanceof JCPrimitiveTypeTree || varType instanceof JCIdent) { + arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m); + } + } else { + arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m); + } + } } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java index a5d4a295..9dab3da5 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -273,12 +273,19 @@ public class JavacSingularsRecipes { generateClearMethod(cfv, deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, access); } - private void finishAndInjectMethod(CheckerFrameworkVersion cfv, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean deprecate, ListBuffer statements, Name methodName, List jcVariableDecls, AccessLevel access) { + private void finishAndInjectMethod(CheckerFrameworkVersion cfv, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean deprecate, ListBuffer statements, Name methodName, List jcVariableDecls, AccessLevel access, NullCollectionBehavior nullBehavior) { if (returnStatement != null) statements.append(returnStatement); JCBlock body = maker.Block(0, statements.toList()); JCModifiers mods = makeMods(maker, cfv, builderType, deprecate, access); List typeParams = List.nil(); List thrown = List.nil(); + + if (nullBehavior == NullCollectionBehavior.IGNORE) { + for (JCVariableDecl d : jcVariableDecls) createRelevantNullableAnnotation(builderType, d); + } else if (nullBehavior != null) { + for (JCVariableDecl d : jcVariableDecls) createRelevantNonNullAnnotation(builderType, d); + } + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, jcVariableDecls, thrown, body, null); recursiveSetGeneratedBy(method, source, builderType.getContext()); injectMethod(builderType, method); @@ -290,7 +297,7 @@ public class JavacSingularsRecipes { statements.add(clearStatement); Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString())); - finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, methodName, List.nil(), access); + finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, methodName, List.nil(), access, null); } protected abstract JCStatement generateClearStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType); @@ -304,7 +311,7 @@ public class JavacSingularsRecipes { if (!setterPrefix.isEmpty()) name = builderType.toName(HandlerUtil.buildAccessorName(setterPrefix, name.toString())); statements.prepend(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); - finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, params, access); + finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, params, access, null); } protected JCVariableDecl generateSingularMethodParameter(int typeIndex, JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source, Name name) { @@ -336,10 +343,11 @@ public class JavacSingularsRecipes { JCExpression paramType = getPluralMethodParamType(builderType); paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs(), source); 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 == null) behavior = NullCollectionBehavior.IGNORE; + JCModifiers paramMods = maker.Modifiers(paramFlags); + JCVariableDecl param = maker.VarDef(paramMods, data.getPluralName(), paramType, null); + statements.prepend(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); if (behavior == NullCollectionBehavior.IGNORE) { JCExpression incomingIsNotNull = maker.Binary(CTC_NOT_EQUAL, maker.Ident(data.getPluralName()), maker.Literal(CTC_BOT, null)); @@ -361,7 +369,7 @@ public class JavacSingularsRecipes { } } - finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, List.of(param), access); + finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, List.of(param), access, behavior); } protected ListBuffer generatePluralMethodStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java index 546dc66e..7cd676c0 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2018 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 @@ -121,14 +121,7 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { @Override protected JCExpression getPluralMethodParamType(JavacNode builderType) { - JCExpression paramType; - String aaTypeName = getAddAllTypeName(); - if (aaTypeName.startsWith("java.lang.") && aaTypeName.indexOf('.', 11) == -1) { - paramType = genJavaLangTypeRef(builderType, aaTypeName.substring(10)); - } else { - paramType = chainDotsString(builderType, aaTypeName); - } - return paramType; + return genTypeRef(builderType, getAddAllTypeName()); } @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer statements, Name targetVariableName, String builderVariable) { -- cgit