diff options
| author | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2020-01-20 15:25:08 +0100 |
|---|---|---|
| committer | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2020-01-28 16:21:39 +0100 |
| commit | e95680a76733c22ee5937a586ee50c703d5ba621 (patch) | |
| tree | 3eaefce07c41760468c3c2a17c86297e2304a730 | |
| parent | fa70b194aa7db62bdbc4cc759a606f97fe50fc92 (diff) | |
| download | lombok-e95680a76733c22ee5937a586ee50c703d5ba621.tar.gz lombok-e95680a76733c22ee5937a586ee50c703d5ba621.tar.bz2 lombok-e95680a76733c22ee5937a586ee50c703d5ba621.zip | |
[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`.
44 files changed, 915 insertions, 128 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 876c810a..8c1b463d 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -5,7 +5,8 @@ Lombok Changelog * PLATFORM: Support for JDK13 (including `yield` in switch expressions, as well as delombok having a nicer style for arrow-style switch blocks, and text blocks). * FEATURE: In [`lombok.config`](https://projectlombok.org/features/configuration) it is possible to import other config files, even from a `.zip` or `.jar`. * FEATURE: You can now configure a builder's 'setter' prefixes via `@Builder(setterPrefix = "set")` for example. We discourage doing this, but if some library you use requires them, have at it. [Pull Request #2174](https://github.com/rzwitserloot/lombok/pull/2174], [Issue #1805](https://github.com/rzwitserloot/lombok/issues/1805). -* FEATURE: Tired of being unable to use `@javax.annotation.ParametersAreNonnullByDefault` or `@org.eclipse.jdt.annotation.NonNullByDefault` because then the equals method that lombok generates isn't valid? Fret no more; lombok will now add the appropriate `@Nullable` annotation to the parameter to avoid any issues. (package-level nonnullByDefault not supported, it must appear on the class for this feature to work). [Issue #788](https://github.com/rzwitserloot/lombok/issues/788) +* FEATURE: If you use `@Builder`'s `@Singular`, a plural form is also generated, which has the effect of adding all elements in the passed collection. If you pass a null reference, this would result in a message-less `NullPointerException`. Now, it results in that exception but with a useful message attached, and you can choose other behaviors as well via a parameter on the `@Singular` annotation and via `lombok.config`; you can even choose treat them as empty collections; this can be useful when deserializing (e.g. Jackson JSON) and JPA/Hibernate code. [Issue #2221](https://github.com/rzwitserloot/lombok/issues/2221]. [singular documentation](https://projectlombok.org/features/Builder). +* FEATURE: Tired of being unable to use `@javax.annotation.ParametersAreNonnullByDefault` or `@org.eclipse.jdt.annotation.NonNullByDefault` because then the equals method that lombok generates isn't valid? Fret no more; lombok can now add nullity annotations where relevant. Set the flavour of nullity annotation you prefer in your `lombok.config`. Applies to the return value of `toString` and `withX` methods, and the parameter of `equals`, `canEqual`, and the plural form of `@Singular` marked fields for builder classes. [Issue #788](https://github.com/rzwitserloot/lombok/issues/788) * BUGFIX: Referring to an inner class inside the generics on a class marked with `@SuperBuilder` would cause the error `wrong number of type arguments; required 3` [Issue #2262](https://github.com/rzwitserloot/lombok/issues/2262); fixed by github user [`@Lekanich`](https://github.com/rzwitserloot/lombok/issues/2262) - thank you! * BUGFIX: Some of the code generated by `@Builder` did not include `this.` prefixes when accessing fields. While semantically it didn't matter, if you use the 'add this prefix for field accesses' save action in eclipse, the save action would break. [Issue #2327](https://github.com/rzwitserloot/lombok/issues/2327) * BUGFIX: When lombok copies javadoc from fields to relevant methods, it should generate an appropriate `@return this` line if lombok copies the javadoc to a generated setter that is chainable (returns itself). It didn't do that when generating the 'setters' in a `@Builder`. Lombok also didn't generate an appropriate `@return` item for `@With` methods. The javadoc has also been updated slightly (the `this` reference in the javadoc is now rendered in a code tag).[Issue #2323](https://github.com/rzwitserloot/lombok/issues/2323) 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<Boolean> ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS = new ConfigurationKey<Boolean>("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.<ul> + * <li>{@code none} (the default) - no annotations are added.</li> + * <li>{@code javax} - The annotations {@code javax.annotation.NonNull} and {@code javax.annotation.Nullable} are used.</li> + * <li>{@code eclipse} - The annotations {@code org.eclipse.jdt.annotation.NonNull} and {@code org.eclipse.jdt.annotation.Nullable} are used.</li> + * <li>{@code jetbrains} - The annotations {@code org.jetbrains.annotations.NotNull} and {@code org.jetbrains.annotations.Nullable} are used.</li> + * <li>{@code netbeans} - The annotations {@code org.netbeans.api.annotations.common.NonNull} and {@code org.netbeans.api.annotations.common.NullAllowed} are used.</li> + * <li>{@code androidx} - The annotations {@code androidx.annotation.NonNull} and {@code androidx.annotation.Nullable} are used.</li> + * <li>{@code android.support} - The annotations {@code android.support.annotation.NonNull} and {@code android.support.annotation.Nullable} are used.</li> + * <li>{@code checkerframework} - The annotations {@code org.checkerframework.checker.nullness.qual.NonNull} and {@code org.checkerframework.checker.nullness.qual.Nullable} are used.</li> + * <li>{@code findbugs} - The annotations {@code edu.umd.cs.findbugs.annotations.NonNull} and {@code edu.umd.cs.findbugs.annotations.Nullable} are used.</li> + * <li>{@code spring} - The annotations {@code org.springframework.lang.NonNull} and {@code org.springframework.lang.Nullable} are used.</li> + * <li>{@code jml} - The annotations {@code org.jmlspecs.annotation.NonNull} and {@code org.jmlspecs.annotation.Nullable} are used.</li> + * <li><code>CUSTOM:<em>fully.qualified.nonnull.annotation</em>:<em>fully.qualified.nullable.annotation</em></code> to configure your own types; the nullable annotation (and the colon) are optional.</li> + * </ul> + * <p> + * Lombok will not put these annotations on the classpath for you; your project must be set up such that these annotations are available. + * <p> + * Current features which use this configuration:<ul> + * <li>{@code @Builder.Singular} makes methods that accept a collection, all of which must be added. The parameter to this 'plural form' method is annotated.</li> + * </ul> + */ + public static final ConfigurationKey<NullAnnotationLibrary> ADD_NULL_ANNOTATIONS = new ConfigurationKey<NullAnnotationLibrary>("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<NullAnnotationLibrary> ALL_AVAILABLE; + private static final String EXAMPLE_VALUE; + + static { + ArrayList<NullAnnotationLibrary> out = new ArrayList<NullAnnotationLibrary>(); + 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<String> 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<Statement> statements) { + protected void nullBehaviorize(EclipseNode typeNode, SingularData data, List<Statement> 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<EqualsAndH method.arguments = new Argument[] {new Argument(new char[] { 'o' }, 0, objectRef, Modifier.FINAL)}; method.arguments[0].sourceStart = pS; method.arguments[0].sourceEnd = pE; if (!onParam.isEmpty()) method.arguments[0].annotations = onParam.toArray(new Annotation[0]); + EclipseHandlerUtil.createRelevantNullableAnnotation(type, method.arguments[0]); setGeneratedBy(method.arguments[0], source); List<Statement> statements = new ArrayList<Statement>(); @@ -806,6 +807,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH method.arguments = new Argument[] {new Argument(otherName, 0, objectRef, Modifier.FINAL)}; method.arguments[0 |
