From c545d0a2d06094cf7104de804f8fe0f49c70b99d Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Thu, 24 Jul 2025 07:36:02 -0700 Subject: [PATCH 1/7] POC for Delegate Copy Annotations --- .../lombok/core/handlers/HandlerUtil.java | 77 ++++++++++++++++++- .../lombok/javac/handlers/HandleDelegate.java | 18 ++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 53ca16f570..02d3f164f7 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -78,8 +78,14 @@ public static int primeForFalse() { public static int primeForNull() { return 43; } - - public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS; + + public static final List NONNULL_ANNOTATIONS, + BASE_COPYABLE_ANNOTATIONS, + JACKSON_COPY_TO_GETTER_ANNOTATIONS, + JACKSON_COPY_TO_SETTER_ANNOTATIONS, + JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, + JACKSON_COPY_TO_BUILDER_ANNOTATIONS; + public static final Set COPYABLE_ANNOTATIONS_FOR_DELEGATE; static { // This is a list of annotations with a __highly specific meaning__: All annotations in this list indicate that passing null for the relevant item is __never__ acceptable, regardless of settings or circumstance. // In other words, things like 'this models a database table, and the db table column has a nonnull constraint', or 'this represents a web form, and if this is null, the form is invalid' __do not count__ and should not be in this list; @@ -489,6 +495,73 @@ public static int primeForNull() { "com.fasterxml.jackson.annotation.JsonView", "com.fasterxml.jackson.databind.annotation.JsonNaming", })); + COPYABLE_ANNOTATIONS_FOR_DELEGATE = Collections.unmodifiableSet(Arrays.asList(new String[] { + "android.annotation.NonNull", + "android.annotation.Nullable", + "android.support.annotation.NonNull", + "android.support.annotation.Nullable", + "android.support.annotation.RecentlyNonNull", + "android.support.annotation.RecentlyNullable", + "androidx.annotation.NonNull", + "androidx.annotation.Nullable", + "androidx.annotation.RecentlyNonNull", + "androidx.annotation.RecentlyNullable", + "com.android.annotations.NonNull", + "com.android.annotations.Nullable", + "com.google.firebase.database.annotations.NotNull", + "com.google.firebase.database.annotations.Nullable", + "com.mongodb.lang.NonNull", + "com.mongodb.lang.Nullable", + "com.sun.istack.NotNull", + "com.sun.istack.Nullable", + "com.unboundid.util.NotNull", + "com.unboundid.util.Nullable", + "edu.umd.cs.findbugs.annotations.CheckForNull", + "edu.umd.cs.findbugs.annotations.NonNull", + "edu.umd.cs.findbugs.annotations.Nullable", + "edu.umd.cs.findbugs.annotations.PossiblyNull", + "edu.umd.cs.findbugs.annotations.UnknownNullness", + "io.micrometer.core.lang.NonNull", + "io.micrometer.core.lang.Nullable", + "io.reactivex.annotations.NonNull", + "io.reactivex.annotations.Nullable", + "io.reactivex.rxjava3.annotations.NonNull", + "io.reactivex.rxjava3.annotations.Nullable", + "jakarta.annotation.Nonnull", + "jakarta.annotation.Nullable", + "javax.annotation.CheckForNull", + "javax.annotation.Nonnull", + "javax.annotation.Nullable", + "libcore.util.NonNull", + "libcore.util.Nullable", + "lombok.NonNull", + "org.checkerframework.checker.nullness.compatqual.NonNullDecl", + "org.checkerframework.checker.nullness.compatqual.NonNullType", + "org.checkerframework.checker.nullness.compatqual.NullableDecl", + "org.checkerframework.checker.nullness.compatqual.NullableType", + "org.checkerframework.checker.nullness.qual.NonNull", + "org.checkerframework.checker.nullness.qual.Nullable", + "org.codehaus.commons.nullanalysis.NotNull", + "org.codehaus.commons.nullanalysis.Nullable", + "org.eclipse.jdt.annotation.NonNull", + "org.eclipse.jdt.annotation.Nullable", + "org.jetbrains.annotations.NotNull", + "org.jetbrains.annotations.Nullable", + "org.jetbrains.annotations.UnknownNullability", + "org.jmlspecs.annotation.NonNull", + "org.jmlspecs.annotation.Nullable", + "org.jspecify.annotations.NonNull", + "org.jspecify.annotations.NonNull", + "org.jspecify.annotations.Nullable", + "org.netbeans.api.annotations.common.CheckForNull", + "org.netbeans.api.annotations.common.NonNull", + "org.netbeans.api.annotations.common.NullAllowed", + "org.netbeans.api.annotations.common.NullUnknown", + "org.springframework.lang.NonNull", + "org.springframework.lang.Nullable", + "reactor.util.annotation.NonNull", + "reactor.util.annotation.Nullable", + })); } /** Checks if the given name is a valid identifier. diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 4d72eea32c..81ba721417 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -71,6 +71,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; +import lombok.core.handlers.HandlerUtil; import lombok.experimental.Delegate; import lombok.javac.FindTypeVarScanner; import lombok.javac.JavacAnnotationHandler; @@ -303,7 +304,10 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na } else { annotations = com.sun.tools.javac.util.List.nil(); } - + for (Compound sigAnnotation : sig.annotations) { + annotations.add(maker.Annotation(sigAnnotation)); + } + JCModifiers mods = maker.Modifiers(PUBLIC, annotations); JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true); boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID; @@ -369,6 +373,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp if (tsym == null) return; for (Symbol member : tsym.getEnclosedElements()) { + ArrayList copyableAnnotations = new ArrayList<>(); for (Compound am : member.getAnnotationMirrors()) { String name = null; try { @@ -378,6 +383,11 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp if ("lombok.Delegate".equals(name) || "lombok.experimental.Delegate".equals(name)) { throw new DelegateRecursion(ct.tsym.name.toString(), member.name.toString()); } + + if (HandlerUtil.COPYABLE_ANNOTATIONS_FOR_DELEGATE.contains(name)) { + copyableAnnotations.add(am); + } + } if (member.getKind() != ElementKind.METHOD) continue; if (member.isStatic()) continue; @@ -403,12 +413,14 @@ public static class MethodSig { final ExecutableType type; final boolean isDeprecated; final ExecutableElement elem; - - MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) { + final List annotations; + + MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem, List annotations) { this.name = name; this.type = type; this.isDeprecated = isDeprecated; this.elem = elem; + this.annotations = annotations; } String[] getParameterNames() { From 9a73e1fa69db8cab01d1ee0690e477e3b77ab29f Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Thu, 24 Jul 2025 07:36:02 -0700 Subject: [PATCH 2/7] POC for Delegate Copy Annotations --- src/core/lombok/core/handlers/HandlerUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 02d3f164f7..a0aa90ff05 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -84,8 +84,8 @@ public static int primeForNull() { JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, - JACKSON_COPY_TO_BUILDER_ANNOTATIONS; - public static final Set COPYABLE_ANNOTATIONS_FOR_DELEGATE; + JACKSON_COPY_TO_BUILDER_ANNOTATIONS, + COPYABLE_ANNOTATIONS_FOR_DELEGATE; static { // This is a list of annotations with a __highly specific meaning__: All annotations in this list indicate that passing null for the relevant item is __never__ acceptable, regardless of settings or circumstance. // In other words, things like 'this models a database table, and the db table column has a nonnull constraint', or 'this represents a web form, and if this is null, the form is invalid' __do not count__ and should not be in this list; @@ -495,7 +495,7 @@ public static int primeForNull() { "com.fasterxml.jackson.annotation.JsonView", "com.fasterxml.jackson.databind.annotation.JsonNaming", })); - COPYABLE_ANNOTATIONS_FOR_DELEGATE = Collections.unmodifiableSet(Arrays.asList(new String[] { + COPYABLE_ANNOTATIONS_FOR_DELEGATE = Collections.unmodifiableList(Arrays.asList(new String[] { "android.annotation.NonNull", "android.annotation.Nullable", "android.support.annotation.NonNull", From ed6c70aba49fac5317c855111ddeabebfbcc8408 Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Thu, 24 Jul 2025 08:04:04 -0700 Subject: [PATCH 3/7] POC for Delegate Copy Annotations --- src/core/lombok/javac/handlers/HandleDelegate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 81ba721417..844361ffbb 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -373,7 +373,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp if (tsym == null) return; for (Symbol member : tsym.getEnclosedElements()) { - ArrayList copyableAnnotations = new ArrayList<>(); + ArrayList copyableAnnotations = new ArrayList(); for (Compound am : member.getAnnotationMirrors()) { String name = null; try { @@ -398,7 +398,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp String sig = printSig(methodType, member.name, types); if (!banList.add(sig)) continue; //If add returns false, it was already in there boolean isDeprecated = (member.flags() & DEPRECATED) != 0; - signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem)); + signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem, copyableAnnotations)); } for (Type type : types.directSupertypes(ct)) { From 2dd3ff12320c4dcd9944fb49c600153a757a31e2 Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Thu, 24 Jul 2025 08:52:45 -0700 Subject: [PATCH 4/7] add passing test --- .../lombok/javac/handlers/HandleDelegate.java | 8 +++----- .../after-delombok/RecordWithNullable.java | 17 +++++++++++++++++ .../resource/before/RecordWithNullable.java | 11 +++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 test/transform/resource/after-delombok/RecordWithNullable.java create mode 100644 test/transform/resource/before/RecordWithNullable.java diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 844361ffbb..d208124381 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -296,19 +296,17 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na JavacTreeMaker maker = annotation.getTreeMaker(); - com.sun.tools.javac.util.List annotations; + ArrayList annotations = new ArrayList(); if (sig.isDeprecated) { - annotations = com.sun.tools.javac.util.List.of(maker.Annotation( + annotations.add(maker.Annotation( genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.nil())); - } else { - annotations = com.sun.tools.javac.util.List.nil(); } for (Compound sigAnnotation : sig.annotations) { annotations.add(maker.Annotation(sigAnnotation)); } - JCModifiers mods = maker.Modifiers(PUBLIC, annotations); + JCModifiers mods = maker.Modifiers(PUBLIC, com.sun.tools.javac.util.List.from(annotations.toArray(new JCAnnotation[0]))); JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true); boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID; ListBuffer params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); diff --git a/test/transform/resource/after-delombok/RecordWithNullable.java b/test/transform/resource/after-delombok/RecordWithNullable.java new file mode 100644 index 0000000000..992c7e469b --- /dev/null +++ b/test/transform/resource/after-delombok/RecordWithNullable.java @@ -0,0 +1,17 @@ +//platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run. +//version 14: +import javax.annotation.Nullable; + +record DelegateOnRecord(SomeInterface runnable) { + interface SomeInterface { + @Nullable + String getString(); + } + + @javax.annotation.Nullable + @java.lang.SuppressWarnings("all") + @lombok.Generated + public java.lang.String getString() { + return this.runnable.getString(); + } +} diff --git a/test/transform/resource/before/RecordWithNullable.java b/test/transform/resource/before/RecordWithNullable.java new file mode 100644 index 0000000000..71199986f7 --- /dev/null +++ b/test/transform/resource/before/RecordWithNullable.java @@ -0,0 +1,11 @@ +//platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run. +//version 14: +import lombok.experimental.Delegate; +import javax.annotation.Nullable; + +record DelegateOnRecord(@Delegate SomeInterface runnable) { + interface SomeInterface { + @Nullable + String getString(); + } +} From 077cc49446d8c11338bcc287006355a00e01b52e Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Fri, 25 Jul 2025 00:56:26 -0700 Subject: [PATCH 5/7] support parameter annotations --- .../lombok/javac/handlers/HandleDelegate.java | 56 ++++++++++++------- .../after-delombok/RecordWithNullable.java | 6 +- .../resource/before/RecordWithNullable.java | 2 +- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index d208124381..739e56cf4e 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -34,11 +34,7 @@ import java.util.List; import java.util.Set; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; +import javax.lang.model.element.*; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -332,17 +328,22 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true)); } - int idx = 0; - String[] paramNames = sig.getParameterNames(); boolean varargs = sig.elem.isVarArgs(); - for (TypeMirror param : sig.type.getParameterTypes()) { + ParameterSig[] paramSigs = sig.getParameters(); + for (int idx = 0; idx < paramSigs.length; idx++) { + ParameterSig paramSig = paramSigs[idx]; long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext()); - JCModifiers paramMods = maker.Modifiers(flags); - Name name = annotation.toName(paramNames[idx++]); - if (varargs && idx == paramNames.length) { + + List paramAnnotations = new ArrayList(); + for (AnnotationMirror b : paramSig.annotations) { + paramAnnotations.add(maker.Annotation((Compound) b)); + } + JCModifiers paramMods = maker.Modifiers(flags, com.sun.tools.javac.util.List.from(paramAnnotations.toArray(new JCAnnotation[0]))); + Name name = annotation.toName(paramSig.name); + if (varargs && idx == paramSigs.length - 1) { paramMods.flags |= VARARGS; } - params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null)); + params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) paramSig.type, annotation.getAst(), true), null)); args.append(maker.Ident(name)); } @@ -421,20 +422,37 @@ public static class MethodSig { this.annotations = annotations; } - String[] getParameterNames() { - List paramList = elem.getParameters(); - String[] paramNames = new String[paramList.size()]; - for (int i = 0; i < paramNames.length; i++) { - paramNames[i] = paramList.get(i).getSimpleName().toString(); + ParameterSig[] getParameters() { + VariableElement[] params = elem.getParameters().toArray(new VariableElement[0]); + TypeMirror[] paramTypes = type.getParameterTypes().toArray(new TypeMirror[0]); + ParameterSig[] parameterSigs = new ParameterSig[params.length]; + for (int i = 0; i < parameterSigs.length; i++) { + parameterSigs[i] = new ParameterSig( + params[i].getSimpleName().toString(), + paramTypes[i], + params[i].getAnnotationMirrors() + ); } - return paramNames; + return parameterSigs; } @Override public String toString() { return (isDeprecated ? "@Deprecated " : "") + name + " " + type; } } - + + public static class ParameterSig { + final String name; + final TypeMirror type; + final List annotations; + + ParameterSig(String name, TypeMirror type, List annotations) { + this.name = name; + this.type = type; + this.annotations = annotations; + } + } + public static String printSig(ExecutableType method, Name name, JavacTypes types) { StringBuilder sb = new StringBuilder(); sb.append(name.toString()).append("("); diff --git a/test/transform/resource/after-delombok/RecordWithNullable.java b/test/transform/resource/after-delombok/RecordWithNullable.java index 992c7e469b..95e25d5947 100644 --- a/test/transform/resource/after-delombok/RecordWithNullable.java +++ b/test/transform/resource/after-delombok/RecordWithNullable.java @@ -5,13 +5,13 @@ record DelegateOnRecord(SomeInterface runnable) { interface SomeInterface { @Nullable - String getString(); + String getString(@Nullable String input); } @javax.annotation.Nullable @java.lang.SuppressWarnings("all") @lombok.Generated - public java.lang.String getString() { - return this.runnable.getString(); + public java.lang.String getString(@javax.annotation.Nullable final java.lang.String input) { + return this.runnable.getString(input); } } diff --git a/test/transform/resource/before/RecordWithNullable.java b/test/transform/resource/before/RecordWithNullable.java index 71199986f7..c75fad6c76 100644 --- a/test/transform/resource/before/RecordWithNullable.java +++ b/test/transform/resource/before/RecordWithNullable.java @@ -6,6 +6,6 @@ record DelegateOnRecord(@Delegate SomeInterface runnable) { interface SomeInterface { @Nullable - String getString(); + String getString(@Nullable String input); } } From 95751771d0f8a7d5845c94b65808e0aa30c8d00c Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Fri, 25 Jul 2025 01:51:49 -0700 Subject: [PATCH 6/7] ensure non copyable annotations are not copied --- src/core/lombok/javac/handlers/HandleDelegate.java | 4 +++- .../resource/after-delombok/RecordWithNullable.java | 4 +++- test/transform/resource/before/RecordWithNullable.java | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 739e56cf4e..0bb663067e 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -336,7 +336,9 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na List paramAnnotations = new ArrayList(); for (AnnotationMirror b : paramSig.annotations) { - paramAnnotations.add(maker.Annotation((Compound) b)); + if (HandlerUtil.COPYABLE_ANNOTATIONS_FOR_DELEGATE.contains(((TypeElement) b.getAnnotationType().asElement()).getQualifiedName().toString())) { + paramAnnotations.add(maker.Annotation((Compound) b)); + } } JCModifiers paramMods = maker.Modifiers(flags, com.sun.tools.javac.util.List.from(paramAnnotations.toArray(new JCAnnotation[0]))); Name name = annotation.toName(paramSig.name); diff --git a/test/transform/resource/after-delombok/RecordWithNullable.java b/test/transform/resource/after-delombok/RecordWithNullable.java index 95e25d5947..9aff300ce2 100644 --- a/test/transform/resource/after-delombok/RecordWithNullable.java +++ b/test/transform/resource/after-delombok/RecordWithNullable.java @@ -1,11 +1,13 @@ //platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run. //version 14: import javax.annotation.Nullable; +import javax.annotation.Tainted; record DelegateOnRecord(SomeInterface runnable) { interface SomeInterface { @Nullable - String getString(@Nullable String input); + @Tainted + String getString(@Nullable @Tainted String input); } @javax.annotation.Nullable diff --git a/test/transform/resource/before/RecordWithNullable.java b/test/transform/resource/before/RecordWithNullable.java index c75fad6c76..5d41c87daa 100644 --- a/test/transform/resource/before/RecordWithNullable.java +++ b/test/transform/resource/before/RecordWithNullable.java @@ -2,10 +2,12 @@ //version 14: import lombok.experimental.Delegate; import javax.annotation.Nullable; +import javax.annotation.Tainted; record DelegateOnRecord(@Delegate SomeInterface runnable) { interface SomeInterface { - @Nullable - String getString(@Nullable String input); + @Nullable + @Tainted + String getString(@Nullable @Tainted String input); } } From 9cd2b464d3192e9a35362eed46a150baa36d547a Mon Sep 17 00:00:00 2001 From: larsbenedetto Date: Fri, 25 Jul 2025 08:57:11 -0700 Subject: [PATCH 7/7] make copy list configurable --- src/core/lombok/ConfigurationKeys.java | 4 ++- .../lombok/javac/handlers/HandleDelegate.java | 30 ++++++++++--------- .../javac/handlers/JavacHandlerUtil.java | 18 +++++++---- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index ce31bddf7f..2a2b4c2105 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -728,7 +728,9 @@ private ConfigurationKeys() {} * Copy these annotations to getters, setters, with methods, builder-setters, etc. */ public static final ConfigurationKey> COPYABLE_ANNOTATIONS = new ConfigurationKey>("lombok.copyableAnnotations", "Copy these annotations to getters, setters, with methods, builder-setters, etc.") {}; - + + public static final ConfigurationKey> COPYABLE_ANNOTATIONS_FOR_DELEGATE = new ConfigurationKey>("lombok.copyableAnnotationsForDelegate", "Copy these annotations to methods generated by @Delegate") {}; + /** * lombok configuration: {@code checkerframework} = {@code true} | {@code false} | <String: MajorVer.MinorVer> (Default: false). * diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 0bb663067e..6f197fb9b3 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -292,17 +292,20 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na JavacTreeMaker maker = annotation.getTreeMaker(); - ArrayList annotations = new ArrayList(); + ArrayList methodAnnotationsToCopy = new ArrayList(); if (sig.isDeprecated) { - annotations.add(maker.Annotation( + methodAnnotationsToCopy.add(maker.Annotation( genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.nil())); } + Set copyableAnnotations = JavacHandlerUtil.getCopyableAnnotationsForDelegate(annotation); for (Compound sigAnnotation : sig.annotations) { - annotations.add(maker.Annotation(sigAnnotation)); + if (copyableAnnotations.contains(sigAnnotation.type.tsym.flatName().toString())) { + methodAnnotationsToCopy.add(maker.Annotation(sigAnnotation)); + } } - JCModifiers mods = maker.Modifiers(PUBLIC, com.sun.tools.javac.util.List.from(annotations.toArray(new JCAnnotation[0]))); + JCModifiers mods = maker.Modifiers(PUBLIC, com.sun.tools.javac.util.List.from(methodAnnotationsToCopy.toArray(new JCAnnotation[0]))); JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true); boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID; ListBuffer params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); @@ -327,20 +330,21 @@ public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Na for (TypeMirror ex : sig.type.getThrownTypes()) { thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true)); } - + boolean varargs = sig.elem.isVarArgs(); ParameterSig[] paramSigs = sig.getParameters(); for (int idx = 0; idx < paramSigs.length; idx++) { ParameterSig paramSig = paramSigs[idx]; long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext()); - List paramAnnotations = new ArrayList(); + List paramAnnotationsToCopy = new ArrayList(); for (AnnotationMirror b : paramSig.annotations) { - if (HandlerUtil.COPYABLE_ANNOTATIONS_FOR_DELEGATE.contains(((TypeElement) b.getAnnotationType().asElement()).getQualifiedName().toString())) { - paramAnnotations.add(maker.Annotation((Compound) b)); + String fqn = ((TypeElement) b.getAnnotationType().asElement()).getQualifiedName().toString(); + if (copyableAnnotations.contains(fqn)) { + paramAnnotationsToCopy.add(maker.Annotation((Compound) b)); } } - JCModifiers paramMods = maker.Modifiers(flags, com.sun.tools.javac.util.List.from(paramAnnotations.toArray(new JCAnnotation[0]))); + JCModifiers paramMods = maker.Modifiers(flags, com.sun.tools.javac.util.List.from(paramAnnotationsToCopy.toArray(new JCAnnotation[0]))); Name name = annotation.toName(paramSig.name); if (varargs && idx == paramSigs.length - 1) { paramMods.flags |= VARARGS; @@ -374,7 +378,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp if (tsym == null) return; for (Symbol member : tsym.getEnclosedElements()) { - ArrayList copyableAnnotations = new ArrayList(); + ArrayList annotations = new ArrayList(); for (Compound am : member.getAnnotationMirrors()) { String name = null; try { @@ -385,9 +389,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp throw new DelegateRecursion(ct.tsym.name.toString(), member.name.toString()); } - if (HandlerUtil.COPYABLE_ANNOTATIONS_FOR_DELEGATE.contains(name)) { - copyableAnnotations.add(am); - } + annotations.add(am); } if (member.getKind() != ElementKind.METHOD) continue; @@ -399,7 +401,7 @@ public void addMethodBindings(List signatures, ClassType ct, JavacTyp String sig = printSig(methodType, member.name, types); if (!banList.add(sig)) continue; //If add returns false, it was already in there boolean isDeprecated = (member.flags() & DEPRECATED) != 0; - signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem, copyableAnnotations)); + signatures.add(new MethodSig(member.name, methodType, isDeprecated, exElem, annotations)); } for (Type type : types.directSupertypes(ct)) { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index f243c56a43..a958a61deb 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -32,10 +32,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; import com.sun.source.tree.TreeVisitor; @@ -1755,7 +1752,18 @@ public static List findCopyableAnnotations(JavacNode node) { return copyAnnotations(result.toList(), node.getTreeMaker()); } - + + public static Set getCopyableAnnotationsForDelegate(JavacNode node) { + HashSet copyable = new HashSet(); + java.util.List configuredCopyable = node.getAst().readConfiguration(ConfigurationKeys.COPYABLE_ANNOTATIONS_FOR_DELEGATE); + if (configuredCopyable != null) { + for (TypeName cn : configuredCopyable) if (cn != null) copyable.add(cn.toString()); + } + copyable.addAll(COPYABLE_ANNOTATIONS_FOR_DELEGATE); + + return copyable; + } + /** * Searches the given field node for annotations that are specifically intended to be copied to the getter. *