diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index acfb3cdc..756b859f 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -2,7 +2,8 @@ Lombok Changelog
----------------
### v1.18.39 "Edgy Guinea Pig"
-* No changes since v1.18.38 yet.
+* FEATURE: `@Jacksonized @Accessors(fluent=true)` automatically creates the relevant annotations such that Jackson correctly identifies fluent accessors.
+* IMPROBABLE BREAKING CHANGE: From versions 1.18.16 to 1.18.38, lombok automatically copies certain Jackson annotations (e.g., `@JsonProperty`) from fields to the corresponding accessors (getters/setters). However, it turned out to be harmful in certain situations. Thus, Lombok does not automatically copy those annotations any more. You can restore the old behavior using the [config key](https://projectlombok.org/features/configuration) `lombok.copyJacksonAnnotationsToAccessors = true`.
### v1.18.38 (March 31st, 2025)
* PLATFORM: JDK24 support added.
diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java
index 06ca3853..efa60710 100644
--- a/src/core/lombok/Builder.java
+++ b/src/core/lombok/Builder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2018 The Project Lombok Authors.
+ * Copyright (C) 2013-2025 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
@@ -46,7 +46,7 @@ import java.lang.annotation.Target;
*
* The TBuilder class contains 1 method for each parameter of the annotated
* constructor / method (each field, when annotating a class), which returns the builder itself.
- * The builder also has a build() method which returns a completed instance of the original type,
+ * The builder also has a {@code build()} method which returns a completed instance of the original type,
* created by passing all parameters as set via the various other methods in the builder to the constructor
* or method that was annotated with {@code @Builder}. The return type of this method will be the same
* as the relevant class, unless a method has been annotated, in which case it'll be equal to the
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java
index 2460edd3..ce31bddf 100644
--- a/src/core/lombok/ConfigurationKeys.java
+++ b/src/core/lombok/ConfigurationKeys.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2023 The Project Lombok Authors.
+ * Copyright (C) 2013-2025 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
@@ -606,14 +606,14 @@ public class ConfigurationKeys {
/**
* lombok configuration: {@code lombok.fieldDefaults.defaultPrivate} = {@code true} | {@code false}.
*
- * If set to trueany field without an access modifier or {@code @PackagePrivate} is marked as {@code private} by lombok, in all source files compiled.
+ * If set to {@code true} any field without an access modifier or {@code @PackagePrivate} is marked as {@code private} by lombok, in all source files compiled.
*/
public static final ConfigurationKey FIELD_DEFAULTS_PRIVATE_EVERYWHERE = new ConfigurationKey("lombok.fieldDefaults.defaultPrivate", "If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.") {};
/**
* lombok configuration: {@code lombok.fieldDefaults.defaultFinal} = {@code true} | {@code false}.
*
- * If set to trueany field without {@code @NonFinal} is marked as {@code final} by lombok, in all source files compiled.
+ * If set to {@code true} any field without {@code @NonFinal} is marked as {@code final} by lombok, in all source files compiled.
*/
public static final ConfigurationKey FIELD_DEFAULTS_FINAL_EVERYWHERE = new ConfigurationKey("lombok.fieldDefaults.defaultFinal", "If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.") {};
@@ -721,7 +721,7 @@ public class ConfigurationKeys {
* If set to {@code true}, no further {@code lombok.config} files will be checked.
*/
public static final ConfigurationKey STOP_BUBBLING = new ConfigurationKey("config.stopBubbling", "Tell the configuration system it should stop looking for other configuration files (default: false).") {};
-
+
/**
* lombok configuration: {@code lombok.copyableAnnotations} += <TypeName: fully-qualified annotation class name>.
*
@@ -737,11 +737,19 @@ public class ConfigurationKeys {
* checkerframework.org version will be generated.
*/
public static final ConfigurationKey CHECKER_FRAMEWORK = new ConfigurationKey("checkerframework", "If set with the version of checkerframework.org (in major.minor, or just 'true' for the latest supported version), create relevant checkerframework.org annotations for code lombok generates (default: false).") {};
-
+
/**
* lombok configuration: {@code lombok.standardException.flagUsage} = {@code WARNING} | {@code ERROR}.
*
* If set, any usage of {@code @StandardException} results in a warning / error.
*/
public static final ConfigurationKey STANDARD_EXCEPTION_FLAG_USAGE = new ConfigurationKey("lombok.standardException.flagUsage", "Emit a warning or error if @StandardException is used.") {};
+
+ /**
+ * lombok configuration: {@code lombok.copyJacksonAnnotationsToAccessors} = {@code true} | {@code false}.
+ *
+ * If {@code true}, copy certain Jackson annotations from a field to its corresponding accessors (getter/setters). This was the behavior from lombok 1.18.16 to 1.18.38.
+ * However, it turned out to be harmful in certain situations. Thus, the default is now {@code false}.
+ */
+ public static final ConfigurationKey COPY_JACKSON_ANNOTATIONS_TO_ACCESSORS = new ConfigurationKey("lombok.copyJacksonAnnotationsToAccessors", "Copy Jackson annotations from fields to the corresponding getters and setters.") {};
}
diff --git a/src/core/lombok/core/configuration/ConfigurationValueType.java b/src/core/lombok/core/configuration/ConfigurationValueType.java
index a44a5a83..c1d2e4e6 100644
--- a/src/core/lombok/core/configuration/ConfigurationValueType.java
+++ b/src/core/lombok/core/configuration/ConfigurationValueType.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Project Lombok Authors.
+ * Copyright (C) 2019-2025 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
@@ -25,9 +25,9 @@ package lombok.core.configuration;
* If a type used in {@link ConfigurationKey} type argument implements this interface,
* it is expected to provide the following three static methods:
*
- *
public static SELF valueOf(String value)
- *
public static String description()
- *
public static String exampleValue()
+ *
public static SELF valueOf(String value)
+ *
public static String description()
+ *
public static String exampleValue()
*
* None of them should throw checked exceptions.
* Based on these methods, an instance of {@link ConfigurationValueParser} is created
diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java
index 0e91edf6..53ca16f5 100644
--- a/src/core/lombok/core/handlers/HandlerUtil.java
+++ b/src/core/lombok/core/handlers/HandlerUtil.java
@@ -79,7 +79,7 @@ public class HandlerUtil {
return 43;
}
- public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS, 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;
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;
@@ -424,7 +424,37 @@ public class HandlerUtil {
"org.checkerframework.common.value.qual.UnknownVal",
"org.checkerframework.framework.qual.PurityUnqualified",
}));
- COPY_TO_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
+
+ // The following two lists contain all annotations that can be copied from the field to the getter or setter.
+ // Right now, it only contains Jackson annotations.
+ // Jackson's annotation processing roughly works as follows: To calculate the annotation for a JSON property, Jackson
+ // builds a triple of the Java field and the corresponding setter and getter methods. It is sufficient for an annotation
+ // to be present on one of those to become effective. E.g., a @JsonIgnore on a setter completely ignores the JSON property,
+ // not only during deserialization, but also when serializing. Therefore, in most cases it is _not_ necessary to copy the
+ // annotations. It may even harm, as Jackson considers some annotations inheritable, and this "virtual inheritance" only
+ // affects annotations on setter/getter, but not on private fields.
+ // However, there are two exceptions where we have to copy the annotations:
+ // 1. When using a builder to deserialize, Jackson does _not_ "propagate" the annotations to the setter methods of the
+ // builder, i.e. annotations like @JsonIgnore on the field will not be respected when deserializing with a builder.
+ // Thus, those annotations should be copied to the builder's setters.
+ // 2. If the getter/setter methods do not follow the exact beanspec naming strategy, Jackson will not correctly detect the
+ // field-getter-setter triple, and annotations may not work as intended.
+ // However, we cannot always know what the user's intention is. Thus, lombok should only fix those cases where it is
+ // obvious what the user wants. That is the case for a `@Jacksonized @Accessors(fluent=true)`.
+ JACKSON_COPY_TO_GETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
+ "com.fasterxml.jackson.annotation.JsonFormat",
+ "com.fasterxml.jackson.annotation.JsonIgnore",
+ "com.fasterxml.jackson.annotation.JsonIgnoreProperties",
+ "com.fasterxml.jackson.annotation.JsonProperty",
+ "com.fasterxml.jackson.annotation.JsonSubTypes",
+ "com.fasterxml.jackson.annotation.JsonTypeInfo",
+ "com.fasterxml.jackson.annotation.JsonUnwrapped",
+ "com.fasterxml.jackson.annotation.JsonView",
+ "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper",
+ "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty",
+ "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText",
+ }));
+ JACKSON_COPY_TO_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"com.fasterxml.jackson.annotation.JacksonInject",
"com.fasterxml.jackson.annotation.JsonAlias",
"com.fasterxml.jackson.annotation.JsonFormat",
@@ -441,9 +471,11 @@ public class HandlerUtil {
"com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty",
"com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText",
}));
- COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
+ JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"com.fasterxml.jackson.annotation.JsonAnySetter",
}));
+ // In order to let Jackson recognize certain configuration annotations when deserializing using a builder, those must
+ // be copied to the generated builder class.
JACKSON_COPY_TO_BUILDER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"com.fasterxml.jackson.annotation.JsonAutoDetect",
"com.fasterxml.jackson.annotation.JsonFormat",
diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java
index df9e5451..bedf46dd 100644
--- a/src/core/lombok/eclipse/TransformEclipseAST.java
+++ b/src/core/lombok/eclipse/TransformEclipseAST.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2023 The Project Lombok Authors.
+ * Copyright (C) 2009-2025 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
@@ -129,14 +129,14 @@ public class TransformEclipseAST {
/**
* Check if lombok already handled the given AST. This method will return
- * true once for diet mode and once for full mode.
+ * {@code true} once for diet mode and once for full mode.
*
* The reason for this is that Eclipse invokes the transform method multiple
* times during compilation and it is enough to transform it once and not
* repeat the whole thing over and over again.
*
* @param ast The AST node belonging to the compilation unit (java speak for a single source file).
- * @return true if this AST was already handled by lombok.
+ * @return {@code true} if this AST was already handled by lombok.
*/
public static boolean alreadyTransformed(CompilationUnitDeclaration ast) {
TransformationState state = CompilationUnitDeclaration_transformationState.get(ast);
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index 1fe09b9b..c45d95a1 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -820,17 +820,36 @@ public class EclipseHandlerUtil {
}
/**
- * Searches the given field node for annotations that are specifically intentioned to be copied to the setter.
+ * Searches the given field node for annotations that are specifically intended to be copied to the getter.
+ *
+ * @param forceCopyJacksonAnnotations If {@code true}, always copy the annotations regardless of regardless of lombok configuration key {@code lombok.copyJacksonAnnotationsToAccessors}.
*/
- public static Annotation[] findCopyableToSetterAnnotations(EclipseNode node) {
- return findAnnotationsInList(node, COPY_TO_SETTER_ANNOTATIONS);
+ public static Annotation[] findCopyableToGetterAnnotations(EclipseNode node, boolean forceCopyJacksonAnnotations) {
+ if (!forceCopyJacksonAnnotations) {
+ Boolean copyAnnotations = node.getAst().readConfiguration(ConfigurationKeys.COPY_JACKSON_ANNOTATIONS_TO_ACCESSORS);
+ if (copyAnnotations == null || !copyAnnotations) return EMPTY_ANNOTATIONS_ARRAY;
+ }
+ return findAnnotationsInList(node, JACKSON_COPY_TO_GETTER_ANNOTATIONS);
}
-
+
/**
- * Searches the given field node for annotations that are specifically intentioned to be copied to the builder's singular method.
+ * Searches the given field node for annotations that are specifically intended to be copied to the setter.
+ *
+ * @param forceCopyJacksonAnnotations If {@code true}, always copy the annotations regardless of regardless of lombok configuration key {@code lombok.copyJacksonAnnotationsToAccessors}.
+ */
+ public static Annotation[] findCopyableToSetterAnnotations(EclipseNode node, boolean forceCopyJacksonAnnotations) {
+ if (!forceCopyJacksonAnnotations) {
+ Boolean copyAnnotations = node.getAst().readConfiguration(ConfigurationKeys.COPY_JACKSON_ANNOTATIONS_TO_ACCESSORS);
+ if (copyAnnotations == null || !copyAnnotations) return EMPTY_ANNOTATIONS_ARRAY;
+ }
+ return findAnnotationsInList(node, JACKSON_COPY_TO_SETTER_ANNOTATIONS);
+ }
+
+ /**
+ * Searches the given field node for annotations that are specifically intended to be copied to the builder's singular method.
*/
public static Annotation[] findCopyableToBuilderSingularSetterAnnotations(EclipseNode node) {
- return findAnnotationsInList(node, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS);
+ return findAnnotationsInList(node, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS);
}
/**
diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java
index 54f1a551..61e49326 100755
--- a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java
+++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2021 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
@@ -422,7 +422,7 @@ public class EclipseSingularsRecipes {
return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS);
}
- /** @return a {@code SingleNameReference} to the builder in the variable builderVariable. If {@ code builderVariable == "this"}, a {@code ThisReference} is returned. */
+ /** @return a {@code SingleNameReference} to the builder in the variable {@code builderVariable}. If {@code builderVariable == "this"}, a {@code ThisReference} is returned. */
protected static Reference getBuilderReference(String builderVariable) {
if ("this".equals(builderVariable)) {
return new ThisReference(0, 0);
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index 8e850f27..96366b9a 100755
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -1042,7 +1042,7 @@ public class HandleBuilder extends EclipseAnnotationHandler {
}
List methodAnnsList = Collections.emptyList();
- Annotation[] methodAnns = EclipseHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode);
+ Annotation[] methodAnns = EclipseHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode, true);
if (methodAnns != null && methodAnns.length > 0) methodAnnsList = Arrays.asList(methodAnns);
ASTNode source = job.sourceNode.get();
MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, bfd.name, bfd.nameOfSetFlag, job.oldChain, toEclipseModifier(job.accessInners),
diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java
index dbe8cc92..9bec523a 100644
--- a/src/core/lombok/eclipse/handlers/HandleGetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleGetter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2024 The Project Lombok Authors.
+ * Copyright (C) 2009-2025 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
@@ -83,7 +83,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
public class HandleGetter extends EclipseAnnotationHandler {
private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0];
private static final String GETTER_NODE_NOT_SUPPORTED_ERR = "@Getter is only supported on a class, an enum, or a field.";
-
+
public boolean generateGetterForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean checkForTypeLevelGetter, List onMethod) {
if (checkForTypeLevelGetter) {
if (hasAnnotation(Getter.class, typeNode)) {
@@ -279,9 +279,15 @@ public class HandleGetter extends EclipseAnnotationHandler {
if (getCheckerFrameworkVersion(fieldNode).generateSideEffectFree()) checkerFramework = new Annotation[] { generateNamedAnnotation(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) };
}
+ // Copying Jackson annotations is required for fluent accessors (otherwise Jackson would not find the accessor).
+ boolean fluent = accessors.isExplicit("fluent");
+ Boolean fluentConfig = fieldNode.getAst().readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT);
+ if (fluentConfig != null && fluentConfig) fluent = fluentConfig;
+
method.annotations = copyAnnotations(source,
onMethod.toArray(new Annotation[0]),
findCopyableAnnotations(fieldNode),
+ findCopyableToGetterAnnotations(fieldNode, fluent),
findDelegatesAndMarkAsHandled(fieldNode),
checkerFramework,
deprecated);
diff --git a/src/core/lombok/eclipse/handlers/HandleJacksonized.java b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
index 466cc338..0a3f5dba 100644
--- a/src/core/lombok/eclipse/handlers/HandleJacksonized.java
+++ b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2021 The Project Lombok Authors.
+ * Copyright (C) 2020-2025 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
@@ -31,6 +31,7 @@ import java.util.List;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
@@ -47,6 +48,7 @@ import lombok.core.handlers.HandlerUtil;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import lombok.spi.Provides;
@@ -62,23 +64,24 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
private static final char[][] JSON_POJO_BUILDER_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder");
private static final char[][] JSON_DESERIALIZE_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonDeserialize");
-
+ private static final char[][] JSON_PROPERTY_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.annotation.JsonProperty");
+
@Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized");
-
+
EclipseNode annotatedNode = annotationNode.up();
EclipseNode tdNode;
- if (annotatedNode.getKind() != Kind.TYPE)
- tdNode = annotatedNode.up(); // @Jacksonized on a constructor or a static factory method.
- else
- tdNode = annotatedNode; // @Jacksonized on the class.
+ if (annotatedNode.getKind() != Kind.TYPE) tdNode = annotatedNode.up(); // @Jacksonized on a constructor or a static factory method.
+ else tdNode = annotatedNode; // @Jacksonized on the class.
TypeDeclaration td = (TypeDeclaration) tdNode.get();
EclipseNode builderAnnotationNode = findAnnotation(Builder.class, annotatedNode);
EclipseNode superBuilderAnnotationNode = findAnnotation(SuperBuilder.class, annotatedNode);
- if (builderAnnotationNode == null && superBuilderAnnotationNode == null) {
- annotationNode.addWarning("@Jacksonized requires @Builder or @SuperBuilder for it to mean anything.");
+ EclipseNode accessorsAnnotationNode = (annotatedNode.getKind() == Kind.TYPE) ? findAnnotation(Accessors.class, annotatedNode) : null;
+
+ if (builderAnnotationNode == null && superBuilderAnnotationNode == null && accessorsAnnotationNode == null) {
+ annotationNode.addWarning("@Jacksonized requires @Builder, @SuperBuilder, or @Accessors for it to mean anything.");
return;
}
@@ -87,6 +90,17 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
return;
}
+ boolean jacksonizedBuilder = builderAnnotationNode != null || superBuilderAnnotationNode != null;
+ if (jacksonizedBuilder) {
+ handleJacksonizedBuilder(ast, annotationNode, annotatedNode, tdNode, td, builderAnnotationNode, superBuilderAnnotationNode);
+ }
+
+ if (accessorsAnnotationNode != null) {
+ handleJacksonizedAccessors(ast, annotationNode, annotatedNode, tdNode, td, accessorsAnnotationNode, jacksonizedBuilder);
+ }
+ }
+
+ private void handleJacksonizedBuilder(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, EclipseNode tdNode, TypeDeclaration td, EclipseNode builderAnnotationNode, EclipseNode superBuilderAnnotationNode) {
boolean isAbstract = (td.modifiers & ClassFileConstants.AccAbstract) != 0;
if (isAbstract) {
annotationNode.addError("Builders on abstract classes cannot be @Jacksonized (the builder would never be used).");
@@ -144,14 +158,45 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate;
}
+ private void handleJacksonizedAccessors(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, EclipseNode tdNode, TypeDeclaration td, EclipseNode accessorsAnnotationNode, boolean jacksonizedBuilder) {
+ AnnotationValues accessorsAnnotation = accessorsAnnotationNode != null ?
+ createAnnotation(Accessors.class, accessorsAnnotationNode) : null;
+ boolean fluent = accessorsAnnotation != null && accessorsAnnotation.getInstance().fluent();
+
+ if (!fluent) {
+ // No changes required for chained-only accessors.
+ if (!jacksonizedBuilder) {
+ annotationNode.addWarning("@Jacksonized only affects fluent accessors (@Accessors(fluent=true)).");
+ }
+ return;
+ }
+
+ // Add @JsonProperty to all fields. It will be automatically copied to the getter/setters later.
+ for (EclipseNode eclipseNode : tdNode.down()) {
+ if (eclipseNode.getKind() == Kind.FIELD) {
+ createJsonPropertyForField(eclipseNode, annotationNode);
+ }
+ }
+ tdNode.rebuild();
+ }
+
+ private void createJsonPropertyForField(EclipseNode fieldNode, EclipseNode annotationNode) {
+ if (hasAnnotation("com.fasterxml.jackson.annotation.JsonProperty", fieldNode)) return;
+ ASTNode astNode = fieldNode.get();
+ if (astNode instanceof FieldDeclaration) {
+ FieldDeclaration fd = (FieldDeclaration)astNode;
+ StringLiteral fieldName = new StringLiteral(fd.name, 0, 0, 0);
+ ((FieldDeclaration) astNode).annotations = addAnnotation(fieldNode.get(), fd.annotations, JSON_PROPERTY_ANNOTATION, fieldName);
+ }
+ }
+
private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, TypeDeclaration td, AnnotationValues builderAnnotation) {
String builderClassName = builderAnnotation != null ?
builderAnnotation.getInstance().builderClassName() : null;
if (builderClassName == null || builderClassName.isEmpty()) {
builderClassName = annotationNode.getAst().readConfiguration(ConfigurationKeys.BUILDER_CLASS_NAME);
- if (builderClassName == null || builderClassName.isEmpty())
- builderClassName = "*Builder";
-
+ if (builderClassName == null || builderClassName.isEmpty()) builderClassName = "*Builder";
+
MethodDeclaration fillParametersFrom = annotatedNode.get() instanceof MethodDeclaration ? (MethodDeclaration) annotatedNode.get() : null;
char[] replacement;
if (fillParametersFrom != null) {
@@ -163,23 +208,22 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
}
builderClassName = builderClassName.replace("*", new String(replacement));
}
-
- if (builderAnnotation == null)
- builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
+
+ if (builderAnnotation == null) builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
return builderClassName;
}
private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0];
-
+
private static Annotation[] findJacksonAnnotationsOnClass(TypeDeclaration td, EclipseNode node) {
if (td.annotations == null) return EMPTY_ANNOTATIONS_ARRAY;
-
+
List result = new ArrayList();
for (Annotation annotation : td.annotations) {
TypeReference typeRef = annotation.type;
if (typeRef != null && typeRef.getTypeName() != null) {
- for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
+ for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
if (typeMatches(bn, node, typeRef)) {
result.add(annotation);
break;
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
index 99552efc..dc2c2139 100644
--- a/src/core/lombok/eclipse/handlers/HandleSetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2024 The Project Lombok Authors.
+ * Copyright (C) 2009-2025 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
@@ -27,6 +27,7 @@ import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -109,6 +110,14 @@ public class HandleSetter extends EclipseAnnotationHandler {
//The annotation will make it happen, so we can skip it.
return;
}
+
+ // If a fluent setter should be generated, force-copy annotations from the field.
+ AnnotationValues accessorsForField = EclipseHandlerUtil.getAccessorsForField(fieldNode);
+ // Copying Jackson annotations is required for fluent accessors (otherwise Jackson would not find the accessor).
+ Annotation[] copyableToSetterAnnotations = findCopyableToSetterAnnotations(fieldNode, accessorsForField.isExplicit("fluent"));
+ onMethod = new ArrayList(onMethod);
+ onMethod.addAll(Arrays.asList(copyableToSetterAnnotations));
+
createSetterForField(level, fieldNode, sourceNode, false, onMethod, onParam);
}
@@ -228,7 +237,7 @@ public class HandleSetter extends EclipseAnnotationHandler {
if (isFieldDeprecated(fieldNode) || deprecate) {
deprecated = new Annotation[] { generateDeprecatedAnnotation(source) };
}
- method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode));
+ method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode, false));
Argument param = new Argument(paramName, p, copyType(field.type, source), Modifier.FINAL);
param.sourceStart = pS; param.sourceEnd = pE;
method.arguments = new Argument[] { param };
diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
index 82e561de..096398b8 100644
--- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2021 The Project Lombok Authors.
+ * Copyright (C) 2013-2025 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
@@ -162,13 +162,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
job.init(annotation, annInstance, annotationNode);
boolean generateBuilderMethod;
- if (job.builderMethodName.isEmpty()) {
- generateBuilderMethod = false;
- } else if (!checkName("builderMethodName", job.builderMethodName, annotationNode)) {
- return;
- } else {
- generateBuilderMethod = true;
- }
+ if (job.builderMethodName.isEmpty()) generateBuilderMethod = false;
+ else if (!checkName("builderMethodName", job.builderMethodName, annotationNode)) return;
+ else generateBuilderMethod = true;
if (!checkName("buildMethodName", job.buildMethodName, annotationNode)) return;
@@ -343,9 +339,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
if (sd == null) continue;
EclipseSingularizer singularizer = sd.getSingularizer();
if (singularizer == null) continue;
- if (singularizer.checkForAlreadyExistingNodesAndGenerateError(job.builderAbstractType, sd)) {
- bfd.singularData = null;
- }
+ if (singularizer.checkForAlreadyExistingNodesAndGenerateError(job.builderAbstractType, sd)) bfd.singularData = null;
}
}
@@ -407,9 +401,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
}
// Let toString() call super.toString() if there is a superclass, so that it also shows fields from the superclass' builder.
MethodDeclaration md = HandleToString.createToString(job.builderType, fieldNodes, true, superclassBuilderClass != null, ast, FieldAccess.ALWAYS_FIELD);
- if (md != null) {
- injectMethod(job.builderType, md);
- }
+ if (md != null) injectMethod(job.builderType, md);
}
if (addCleaning) {
@@ -593,7 +585,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
char[][] setVariableInBuilder = new char[][] {BUILDER_VARIABLE_NAME, fieldNode.nameOfSetFlag};
long[] positions = new long[] {p, p};
QualifiedNameReference setVariableInBuilderRef = new QualifiedNameReference(setVariableInBuilder, positions, s, e);
-
+
MessageSend defaultMethodCall = new MessageSend();
defaultMethodCall.sourceStart = job.source.sourceStart;
defaultMethodCall.sourceEnd = job.source.sourceEnd;
@@ -657,7 +649,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
}
/**
- * Generates a toBuilder() method in the annotated class that looks like this:
+ * Generates a {@code toBuilder()} method in the annotated class that looks like this:
*
* public Foobar.FoobarBuilder<?, ?> toBuilder() {
* return new .FoobarBuilderImpl().$fillValuesFrom(this);
@@ -696,10 +688,10 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
out.traverse(new SetGeneratedByVisitor(job.source), ((TypeDeclaration) job.parentType.get()).scope);
return out;
}
-
+
/**
- * Generates a $fillValuesFrom() method in the abstract builder class that looks
- * like this:
+ * Generates a {@code $fillValuesFrom()} method in the abstract builder class.
+ * It looks like:
*
* protected B $fillValuesFrom(final C instance) {
* super.$fillValuesFrom(instance);
@@ -718,9 +710,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
TypeReference builderType = new SingleTypeReference(classGenericName.toCharArray(), 0);
out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, builderType, Modifier.FINAL)};
-
+
List body = new ArrayList();
-
+
if (inherited) {
// Call super.
MessageSend callToSuper = new MessageSend();
@@ -729,14 +721,14 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
callToSuper.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0)};
body.add(callToSuper);
}
-
+
// Call the builder implemention's helper method that actually fills the values from the instance.
MessageSend callStaticFillValuesMethod = new MessageSend();
callStaticFillValuesMethod.receiver = generateNameReference(job.parentType, job.builderAbstractClassNameArr, 0);
callStaticFillValuesMethod.selector = FILL_VALUES_STATIC_METHOD_NAME;
callStaticFillValuesMethod.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0), new ThisReference(0, 0)};
body.add(callStaticFillValuesMethod);
-
+
// Return self().
MessageSend returnCall = new MessageSend();
returnCall.receiver = ThisReference.implicitThis();
@@ -747,9 +739,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
return out;
}
-
+
/**
- * Generates a $fillValuesFromInstanceIntoBuilder() method in
+ * Generates a {@code $fillValuesFromInstanceIntoBuilder()} method in
* the builder implementation class that copies all fields from the instance
* to the builder. It looks like this:
*
@@ -1007,7 +999,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
if (Arrays.equals(setterName.toCharArray(), existingName) && !isTolerate(fieldNode, existing[i])) return;
}
- List methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode));
+ List methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode, true));
addCheckerFrameworkReturnsReceiver(returnType, job.source, job.checkerFramework);
MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, paramName, nameOfSetFlag, returnType, returnStatement, ClassFileConstants.AccPublic,
job.sourceNode, methodAnnsList, annosOnParam != null ? Arrays.asList(copyAnnotations(job.source, annosOnParam)) : Collections.emptyList());
@@ -1095,8 +1087,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
java.util.HashSet usedNames = new HashSet();
// 1. Add type parameter names.
- for (TypeParameter typeParam : typeParams)
- usedNames.add(typeParam.toString());
+ for (TypeParameter typeParam : typeParams) usedNames.add(typeParam.toString());
// 2. Add class name.
usedNames.add(String.valueOf(td.name));
@@ -1119,14 +1110,12 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
return usedNames;
}
-
+
private void addFirstToken(java.util.Set usedNames, TypeReference type) {
- if (type == null)
- return;
+ if (type == null) return;
// Add the first token, because only that can collide.
char[][] typeName = type.getTypeName();
- if (typeName != null && typeName.length >= 1)
- usedNames.add(String.valueOf(typeName[0]));
+ if (typeName != null && typeName.length >= 1) usedNames.add(String.valueOf(typeName[0]));
}
private String generateNonclashingNameFor(String classGenericName, java.util.Set typeParamStrings) {
@@ -1227,8 +1216,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler {
// Cannot use typeMatches() here, because the parameter could be fully-qualified, partially-qualified, or not qualified.
// A string-compare of the last part should work. If it's a false-positive, users could still @Tolerate it.
char[] typeName = def.arguments[0].type.getLastToken();
- if (builderClassName.equals(String.valueOf(typeName)))
- return true;
+ if (builderClassName.equals(String.valueOf(typeName))) return true;
}
}
}
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
index a43e0331..85c7d605 100755
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2021 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
@@ -219,7 +219,7 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer {
char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray();
md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName(builderType, "addAll", new String(data.getPluralName())).toCharArray();
Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource());
- Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up()));
+ Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), true));
md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class);
if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md);
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
index 810a7878..2802bf7a 100755
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
@@ -195,7 +195,7 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula
char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray();
md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName(builderType, "addAll", new String(data.getPluralName())).toCharArray();
Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource());
- Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up()));
+ Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), true));
md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class);
if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md);
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
index 16ed2d44..eee849f2 100755
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2021 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
@@ -332,7 +332,7 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer
md.selector = setterName.toCharArray();
Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource());
- Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up()));
+ Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), true));
md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class);
if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md);
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java
index a432f3fe..be6d13f7 100644
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2021 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
diff --git a/src/core/lombok/experimental/SuperBuilder.java b/src/core/lombok/experimental/SuperBuilder.java
index 193bda0f..630fb73b 100644
--- a/src/core/lombok/experimental/SuperBuilder.java
+++ b/src/core/lombok/experimental/SuperBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Project Lombok Authors.
+ * Copyright (C) 2018-2025 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
@@ -40,7 +40,7 @@ import lombok.Singular;
*
* The TBuilder class contains 1 method for each parameter of the annotated
* constructor / method (each field, when annotating a class), which returns the builder itself.
- * The builder also has a build() method which returns a completed instance of the original type.
+ * The builder also has a {@code build()} method which returns a completed instance of the original type.
*
* Complete documentation is found at the project lombok features page for @SuperBuilder.
*
@@ -56,8 +56,8 @@ public @interface SuperBuilder {
String buildMethodName() default "build";
/**
- * If true, generate an instance method to obtain a builder that is initialized with the values of this instance.
- * In this case, all superclasses must also have toBuilder=true.
+ * If {@code true}, generate an instance method to obtain a builder that is initialized with the values of this instance.
+ * In this case, all superclasses must also have {@code toBuilder=true}.
*
* @return Whether to generate a {@code toBuilder()} method.
*/
diff --git a/src/core/lombok/experimental/UtilityClass.java b/src/core/lombok/experimental/UtilityClass.java
index f8281660..81c01cec 100644
--- a/src/core/lombok/experimental/UtilityClass.java
+++ b/src/core/lombok/experimental/UtilityClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2019 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
*
If any constructors are declared in it, an error is generated. Otherwise, a private no-args constructor is generated; it throws a {@code UnsupportedOperationException}.
*
All methods, inner classes, and fields in the class are marked static.
*
WARNING: Do not use non-star static imports to import these members; javac won't be able to figure it out. Use either:
- * import static ThisType.*; or don't static-import.
+ * {@code import static ThisType.*;} or don't static-import.
*
*/
@Target({ElementType.TYPE})
diff --git a/src/core/lombok/extern/jackson/Jacksonized.java b/src/core/lombok/extern/jackson/Jacksonized.java
index 801ddbcb..5e28bf07 100644
--- a/src/core/lombok/extern/jackson/Jacksonized.java
+++ b/src/core/lombok/extern/jackson/Jacksonized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Project Lombok Authors.
+ * Copyright (C) 2020-2025 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
@@ -28,12 +28,21 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import lombok.Builder;
+import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
/**
* The {@code @Jacksonized} annotation is an add-on annotation for
- * {@code @}{@link Builder} and {@code @}{@link SuperBuilder}. It automatically
- * configures the generated builder class to be used by Jackson's
+ * {@code @}{@link Builder}, {@code @}{@link SuperBuilder}, and
+ * {@code @}{@link Accessors}.
+ *
+ * For {@code @}{@link Accessors}{@code (fluent = true)} on a type, it automatically
+ * configures Jackson to use the generated setters and getters for
+ * (de-)serialization by inserting {@code @}{@link JsonProperty} annotations.
+ * (Note that {@code @Jacksonized} {@code @Accessors} on fields are not supported.)
+ *
+ * For {@code @}{@link Builder} and {@code @}{@link SuperBuilder}, it
+ * automatically configures the generated builder class to be used by Jackson's
* deserialization. It only has an effect if present at a context where there is
* also a {@code @Builder} or a {@code @SuperBuilder}; a warning is emitted
* otherwise.
@@ -41,8 +50,8 @@ import lombok.experimental.SuperBuilder;
* In particular, the annotation does the following:
*
*
Configure Jackson to use the builder for deserialization using
- * {@code @JsonDeserialize(builder=Foobar.FoobarBuilder[Impl].class)}
- * on the class (where Foobar is the name of the annotated class).
+ * {@code @JsonDeserialize(builder=Foobar.FoobarBuilder[Impl].class)} on the
+ * class (where Foobar is the name of the annotated class).
*
Copy Jackson-related configuration annotations (like
* {@code @JsonIgnoreProperties}) from the class to the builder class. This is
* necessary so that Jackson recognizes them when using the builder.
For {@code @SuperBuilder}, make the builder implementation class
* package-private.
*
- * This annotation does not change the behavior of the generated builder.
- * A {@code @Jacksonized} {@code @SuperBuilder} remains fully compatible to
- * regular {@code @SuperBuilder}s.
+ * This annotation does not change the behavior of the generated
+ * builder. A {@code @Jacksonized} {@code @SuperBuilder} remains fully
+ * compatible to regular {@code @SuperBuilder}s.
*/
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index d814c05a..1e023f20 100644
--- a/src/core/lombok/javac/handlers/HandleBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2021 The Project Lombok Authors.
+ * Copyright (C) 2013-2025 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
@@ -930,7 +930,7 @@ public class HandleBuilder extends JavacAnnotationHandler {
JavacTreeMaker maker = fieldNode.getTreeMaker();
- List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode);
+ List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode, true);
JCMethodDecl newMethod = HandleSetter.createSetter(toJavacModifier(job.accessInners), deprecate, fieldNode, maker, setterName, bfd.name, bfd.nameOfSetFlag, job.oldChain, job.sourceNode, methodAnns, bfd.annotations);
recursiveSetGeneratedBy(newMethod, job.sourceNode);
if (job.sourceNode.up().getKind() == Kind.METHOD) {
diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java
index 03dd566c..f6b28ffb 100644
--- a/src/core/lombok/javac/handlers/HandleGetter.java
+++ b/src/core/lombok/javac/handlers/HandleGetter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2024 The Project Lombok Authors.
+ * Copyright (C) 2009-2025 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
@@ -249,8 +249,15 @@ public class HandleGetter extends JavacAnnotationHandler {
JCExpression annotationMethodDefaultValue = null;
List copyableAnnotations = findCopyableAnnotations(field);
+
+ // Copying Jackson annotations is required for fluent accessors (otherwise Jackson would not find the accessor).
+ boolean fluent = accessors.isExplicit("fluent");
+ Boolean fluentConfig = field.getAst().readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT);
+ if (fluentConfig != null && fluentConfig) fluent = fluentConfig;
+ List copyableToGetterAnnotations = copyAnnotations(findCopyableToGetterAnnotations(field, fluent), treeMaker);
+
List delegates = findDelegatesAndRemoveFromField(field);
- List annsOnMethod = copyAnnotations(onMethod, treeMaker).appendList(copyableAnnotations);
+ List annsOnMethod = copyAnnotations(onMethod, treeMaker).appendList(copyableAnnotations).appendList(copyableToGetterAnnotations);
if (field.isFinal()) {
if (getCheckerFrameworkVersion(field).generatePure()) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genTypeRef(field, CheckerFrameworkVersion.NAME__PURE), List.nil()));
} else {
diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java
index bec2d771..689e3d6d 100644
--- a/src/core/lombok/javac/handlers/HandleJacksonized.java
+++ b/src/core/lombok/javac/handlers/HandleJacksonized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020-2021 The Project Lombok Authors.
+ * Copyright (C) 2020-2025 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
@@ -33,6 +33,7 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
@@ -42,6 +43,7 @@ import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.handlers.HandlerUtil;
+import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import lombok.javac.JavacAnnotationHandler;
@@ -55,34 +57,76 @@ import lombok.spi.Provides;
* needs for builders.
*/
@Provides
-@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated).
+@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated), but before all handlers generating getters/setters.
public class HandleJacksonized extends JavacAnnotationHandler {
@Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized");
-
+
JavacNode annotatedNode = annotationNode.up();
deleteAnnotationIfNeccessary(annotationNode, Jacksonized.class);
JavacNode tdNode;
- if (annotatedNode.getKind() != Kind.TYPE)
- tdNode = annotatedNode.up(); // @Jacksonized on a constructor or a static factory method.
- else
- tdNode = annotatedNode; // @Jacksonized on the class.
+ if (annotatedNode.getKind() != Kind.TYPE) tdNode = annotatedNode.up(); // @Jacksonized on a constructor or a static factory method.
+ else tdNode = annotatedNode; // @Jacksonized on the class.
JCClassDecl td = (JCClassDecl) tdNode.get();
-
+
JavacNode builderAnnotationNode = findAnnotation(Builder.class, annotatedNode);
JavacNode superBuilderAnnotationNode = findAnnotation(SuperBuilder.class, annotatedNode);
- if (builderAnnotationNode == null && superBuilderAnnotationNode == null) {
- annotationNode.addWarning("@Jacksonized requires @Builder or @SuperBuilder for it to mean anything.");
+ JavacNode accessorsAnnotationNode = findAnnotation(Accessors.class, annotatedNode);
+ if (builderAnnotationNode == null && superBuilderAnnotationNode == null && accessorsAnnotationNode == null) {
+ annotationNode.addWarning("@Jacksonized requires @Builder, @SuperBuilder, or @Accessors for it to mean anything.");
return;
}
-
+
+ if (builderAnnotationNode != null || superBuilderAnnotationNode != null) {
+ handleJacksonizedBuilder(annotationNode, annotatedNode, tdNode, td, builderAnnotationNode, superBuilderAnnotationNode);
+ }
+
+ if (accessorsAnnotationNode != null) {
+ handleJacksonizedAccessors(annotationNode, annotatedNode, tdNode, td, accessorsAnnotationNode, builderAnnotationNode != null || superBuilderAnnotationNode != null);
+ }
+ }
+
+ private void handleJacksonizedAccessors(JavacNode annotationNode, JavacNode annotatedNode, JavacNode tdNode, JCClassDecl td, JavacNode accessorsAnnotationNode, boolean jacksonizedBuilder) {
+ AnnotationValues accessorsAnnotation = accessorsAnnotationNode != null ?
+ createAnnotation(Accessors.class, accessorsAnnotationNode) : null;
+ boolean fluent = accessorsAnnotation != null && accessorsAnnotation.getInstance().fluent();
+
+ if (!fluent) {
+ // No changes required for chained-only accessors.
+ if (!jacksonizedBuilder)
+ annotationNode.addWarning("@Jacksonized only affects fluent accessors (@Accessors(fluent=true)).");
+ return;
+ }
+
+ // Add @JsonProperty to all fields. It will be automatically copied to the getter/setters later.
+ for (JavacNode javacNode : tdNode.down()) {
+ if (javacNode.getKind() == Kind.FIELD) {
+ createJsonPropertyForField(javacNode, annotationNode);
+ }
+ }
+ }
+
+ private void createJsonPropertyForField(JavacNode fieldNode, JavacNode annotationNode) {
+ if (hasAnnotation("com.fasterxml.jackson.annotation.JsonProperty", fieldNode)) {
+ return;
+ }
+ JavacTreeMaker maker = fieldNode.getTreeMaker();
+
+ JCExpression jsonPropertyType = chainDots(fieldNode, "com", "fasterxml", "jackson", "annotation", "JsonProperty");
+ JCAnnotation annotationJsonProperty = maker.Annotation(jsonPropertyType, List.of(maker.Literal(fieldNode.getName())));
+ recursiveSetGeneratedBy(annotationJsonProperty, annotationNode);
+ JCVariableDecl fieldDecl = ((JCVariableDecl)fieldNode.get());
+ fieldDecl.mods.annotations = fieldDecl.mods.annotations.append(annotationJsonProperty);
+ }
+
+ private void handleJacksonizedBuilder(JavacNode annotationNode, JavacNode annotatedNode, JavacNode tdNode, JCClassDecl td, JavacNode builderAnnotationNode, JavacNode superBuilderAnnotationNode) {
if (builderAnnotationNode != null && superBuilderAnnotationNode != null) {
annotationNode.addError("@Jacksonized cannot process both @Builder and @SuperBuilder on the same class.");
return;
}
-
+
boolean isAbstract = (td.mods.flags & Flags.ABSTRACT) != 0;
if (isAbstract) {
annotationNode.addError("Builders on abstract classes cannot be @Jacksonized (the builder would never be used).");
@@ -90,24 +134,22 @@ public class HandleJacksonized extends JavacAnnotationHandler {
}
AnnotationValues builderAnnotation = builderAnnotationNode != null ?
- createAnnotation(Builder.class, builderAnnotationNode) :
- null;
+ createAnnotation(Builder.class, builderAnnotationNode) : null;
AnnotationValues superBuilderAnnotation = superBuilderAnnotationNode != null ?
- createAnnotation(SuperBuilder.class, superBuilderAnnotationNode) :
- null;
+ createAnnotation(SuperBuilder.class, superBuilderAnnotationNode) : null;
String setPrefix = builderAnnotation != null ?
builderAnnotation.getInstance().setterPrefix() :
- superBuilderAnnotation.getInstance().setterPrefix();
+ superBuilderAnnotation.getInstance().setterPrefix();
String buildMethodName = builderAnnotation != null ?
builderAnnotation.getInstance().buildMethodName() :
- superBuilderAnnotation.getInstance().buildMethodName();
+ superBuilderAnnotation.getInstance().buildMethodName();
JavacTreeMaker maker = annotatedNode.getTreeMaker();
-
+
// Now lets find the generated builder class.
String builderClassName = getBuilderClassName(annotationNode, annotatedNode, td, builderAnnotation, maker);
-
+
JCClassDecl builderClass = null;
for (JCTree member : td.getMembers()) {
if (member instanceof JCClassDecl && ((JCClassDecl) member).getSimpleName().contentEquals(builderClassName)) {
@@ -149,20 +191,17 @@ public class HandleJacksonized extends JavacAnnotationHandler {
JCAnnotation annotationJsonPOJOBuilder = maker.Annotation(jsonPOJOBuilderType, List.of(withPrefixExpr, buildMethodNameExpr));
recursiveSetGeneratedBy(annotationJsonPOJOBuilder, annotatedNode);
builderClass.mods.annotations = builderClass.mods.annotations.append(annotationJsonPOJOBuilder);
-
+
// @SuperBuilder? Make it package-private!
- if (superBuilderAnnotationNode != null)
- builderClass.mods.flags = builderClass.mods.flags & ~Flags.PRIVATE;
- }
-
+ if (superBuilderAnnotationNode != null) builderClass.mods.flags = builderClass.mods.flags & ~Flags.PRIVATE;
+ }
+
private String getBuilderClassName(JavacNode annotationNode, JavacNode annotatedNode, JCClassDecl td, AnnotationValues builderAnnotation, JavacTreeMaker maker) {
- String builderClassName = builderAnnotation != null ?
- builderAnnotation.getInstance().builderClassName() : null;
+ String builderClassName = builderAnnotation != null ? builderAnnotation.getInstance().builderClassName() : null;
if (builderClassName == null || builderClassName.isEmpty()) {
builderClassName = annotationNode.getAst().readConfiguration(ConfigurationKeys.BUILDER_CLASS_NAME);
- if (builderClassName == null || builderClassName.isEmpty())
- builderClassName = "*Builder";
-
+ if (builderClassName == null || builderClassName.isEmpty()) builderClassName = "*Builder";
+
JCMethodDecl fillParametersFrom = annotatedNode.get() instanceof JCMethodDecl ? (JCMethodDecl)annotatedNode.get() : null;
String replacement;
if (fillParametersFrom != null && !fillParametersFrom.getName().toString().equals("")) {
@@ -179,9 +218,8 @@ public class HandleJacksonized extends JavacAnnotationHandler {
}
builderClassName = builderClassName.replace("*", replacement);
}
-
- if (builderAnnotation == null)
- builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
+
+ if (builderAnnotation == null) builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
return builderClassName;
}
@@ -191,7 +229,7 @@ public class HandleJacksonized extends JavacAnnotationHandler {
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
JCAnnotation annotation = (JCAnnotation) child.get();
- for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
+ for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
if (typeMatches(bn, node, annotation.annotationType)) {
result.append(annotation);
break;
diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java
index bdb5b1ac..0764c768 100644
--- a/src/core/lombok/javac/handlers/HandleSetter.java
+++ b/src/core/lombok/javac/handlers/HandleSetter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2024 The Project Lombok Authors.
+ * Copyright (C) 2009-2025 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,7 +211,7 @@ public class HandleSetter extends JavacAnnotationHandler {
return createSetter(access, deprecate, field, treeMaker, setterName, paramName, booleanFieldToSet, returnType, returnStatement, source, onMethod, onParam);
}
- public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name paramName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List onMethod, List onParam, JCVariableDecl recv) {
+ public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name paramName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List onMethod, List onParam, JCVariableDecl recv, boolean forceAnnotationCopying) {
JCExpression returnType = null;
JCReturn returnStatement = null;
if (shouldReturnThis) {
@@ -274,12 +274,17 @@ public class HandleSetter extends JavacAnnotationHandler {
List throwsClauses = List.nil();
JCExpression annotationMethodDefaultValue = null;
- List annsOnMethod = mergeAnnotations(copyAnnotations(onMethod, treeMaker), findCopyableToSetterAnnotations(field));
+ // Copying Jackson annotations is required for fluent accessors (otherwise Jackson would not find the accessor).
+ AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field);
+ boolean fluent = accessors.isExplicit("fluent");
+ Boolean fluentConfig = field.getAst().readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT);
+ if (fluentConfig != null && fluentConfig) fluent = fluentConfig;
+
+ List annsOnMethod = mergeAnnotations(copyAnnotations(onMethod, treeMaker), findCopyableToSetterAnnotations(field, fluent));
if (isFieldDeprecated(field) || deprecate) {
annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil()));
}
- AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field);
if (shouldMakeFinal(field, accessors)) access |= Flags.FINAL;
JCMethodDecl methodDef;
if (recv != null && treeMaker.hasMethodDefWithRecvParam()) {
diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
index c468fb77..11b3d39d 100644
--- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2021 The Project Lombok Authors.
+ * Copyright (C) 2013-2025 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
@@ -143,13 +143,10 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
job.init(annotation, annInstance, annotationNode);
boolean generateBuilderMethod;
- if (job.builderMethodName.isEmpty()) {
- generateBuilderMethod = false;
- } else if (!checkName("builderMethodName", job.builderMethodName, annotationNode)) {
- return;
- } else {
- generateBuilderMethod = true;
- }
+ if (job.builderMethodName.isEmpty()) generateBuilderMethod = false;
+ else if (!checkName("builderMethodName", job.builderMethodName, annotationNode)) return;
+ else generateBuilderMethod = true;
+
if (!checkName("buildMethodName", job.buildMethodName, annotationNode)) return;
// Do not delete the SuperBuilder annotation here, we need it for @Jacksonized.
@@ -313,9 +310,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
if (sd == null) continue;
JavacSingularizer singularizer = sd.getSingularizer();
if (singularizer == null) continue;
- if (singularizer.checkForAlreadyExistingNodesAndGenerateError(job.builderAbstractType, sd)) {
- bfd.singularData = null;
- }
+ if (singularizer.checkForAlreadyExistingNodesAndGenerateError(job.builderAbstractType, sd)) bfd.singularData = null;
}
}
@@ -389,7 +384,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
}
sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(job.builderImplType, annotationNode);
}
-
+
// Create a simple constructor for the BuilderImpl class.
JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.nil(), job.builderImplType, List.nil(), false, annotationNode);
if (cd != null) injectMethod(job.builderImplType, cd);
@@ -515,7 +510,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
recursiveSetGeneratedBy(builder, job.sourceNode);
return injectType(job.parentType, builder);
}
-
+
/**
* Generates a constructor that has a builder as the only parameter.
* The values from the builder are used to initialize the fields of new instances.
@@ -582,8 +577,8 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
if (callBuilderBasedSuperConstructor) {
// The first statement must be the call to the super constructor.
JCMethodInvocation callToSuperConstructor = maker.Apply(List.nil(),
- maker.Ident(job.toName("super")),
- List.of(maker.Ident(builderVariableName)));
+ maker.Ident(job.toName("super")),
+ List.of(maker.Ident(builderVariableName)));
statements.prepend(maker.Exec(callToSuperConstructor));
}
@@ -624,7 +619,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
}
/**
- * Generates a toBuilder() method in the annotated class that looks like this:
+ * Generates a {@code toBuilder()} method in the annotated class.
+ *
+ * It looks like:
*
* public ParentBuilder<?, ?> toBuilder() {
* return new FoobarBuilderImpl().$fillValuesFrom(this);
@@ -657,10 +654,11 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
createRelevantNonNullAnnotation(job.parentType, methodDef);
return methodDef;
}
-
+
/**
- * Generates a $fillValuesFrom() method in the abstract builder class that looks
- * like this:
+ * Generates a {@code $fillValuesFrom()} method in the abstract builder class.
+ *
+ * It looks like:
*
* protected B $fillValuesFrom(final C instance) {
* super.$fillValuesFrom(instance);
@@ -682,7 +680,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
JCExpression classGenericNameExpr = maker.Ident(job.toName(classGenericName));
JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.PARAMETER | Flags.FINAL), job.toName(INSTANCE_VARIABLE_NAME), classGenericNameExpr, null);
-
+
ListBuffer body = new ListBuffer();
if (inherited) {
@@ -706,11 +704,12 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
return maker.MethodDef(modifiers, name, returnType, List.nil(), List.of(param), List.nil(), bodyBlock, null);
}
-
+
/**
- * Generates a $fillValuesFromInstanceIntoBuilder() method in
+ * Generates a {@code $fillValuesFromInstanceIntoBuilder()} method in
* the builder implementation class that copies all fields from the instance
- * to the builder. It looks like this:
+ * to the builder.
+ * It looks like:
*
*
* protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder<?, ?> b) {
@@ -727,7 +726,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
// 1st parameter: "Foobar instance"
JCVariableDecl paramInstance = maker.VarDef(maker.Modifiers(Flags.PARAMETER | Flags.FINAL), job.toName(INSTANCE_VARIABLE_NAME), cloneSelfType(job.parentType), null);
-
+
// 2nd parameter: "FoobarBuilder, ?> b" (plus generics on the annotated type)
// First add all generics that are present on the parent type.
ListBuffer typeParamsForBuilderParameter = getTypeParamExpressions(job.typeParams, maker, job.sourceNode);
@@ -800,7 +799,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
Name name = job.toName(SELF_METHOD);
JCExpression returnType = maker.Ident(job.toName(builderGenericName));
returnType = addCheckerFrameworkReturnsReceiver(returnType, maker, job.builderType, job.checkerFramework);
-
+
return maker.MethodDef(modifiers, name, returnType, List.nil(), List.nil(), List.nil(), null, null);
}
@@ -965,7 +964,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
JavacTreeMaker maker = fieldNode.getTreeMaker();
- List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode);
+ List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode, true);
returnType = addCheckerFrameworkReturnsReceiver(returnType, maker, job.builderType, job.checkerFramework);
JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, paramName, nameOfSetFlag, returnType, returnStatement, job.sourceNode, methodAnns, annosOnParam);
@@ -1046,8 +1045,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
java.util.HashSet usedNames = new HashSet();
// 1. Add type parameter names.
- for (JCTypeParameter typeParam : typeParams)
- usedNames.add(typeParam.getName().toString());
+ for (JCTypeParameter typeParam : typeParams) usedNames.add(typeParam.getName().toString());
// 2. Add class name.
usedNames.add(td.name.toString());
@@ -1056,16 +1054,13 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
for (JCTree member : td.getMembers()) {
if (member.getKind() == com.sun.source.tree.Tree.Kind.VARIABLE && member instanceof JCVariableDecl) {
JCTree type = ((JCVariableDecl)member).getType();
- if (type instanceof JCIdent)
- usedNames.add(((JCIdent)type).getName().toString());
+ if (type instanceof JCIdent) usedNames.add(((JCIdent)type).getName().toString());
}
}
// 4. Add extends and implements clauses.
addFirstToken(usedNames, Javac.getExtendsClause(td));
- for (JCExpression impl : td.getImplementsClause()) {
- addFirstToken(usedNames, impl);
- }
+ for (JCExpression impl : td.getImplementsClause()) addFirstToken(usedNames, impl);
return usedNames;
}
@@ -1078,7 +1073,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
}
while (type instanceof JCFieldAccess && ((JCFieldAccess)type).selected != null) {
// Add the first token, because only that can collide.
- type = ((JCFieldAccess)type).selected;
+ type = ((JCFieldAccess) type).selected;
}
usedNames.add(type.toString());
}
@@ -1118,7 +1113,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
}
return typeParamsForBuilderParameter;
}
-
+
private JCExpression copySelect(JavacTreeMaker maker, JCFieldAccess typeParam) {
java.util.List chainNames = new ArrayList();
JCExpression expression = typeParam;
@@ -1131,7 +1126,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
expression = null;
}
}
-
+
Collections.reverse(chainNames);
JCExpression typeParameter = null;
for (Name name : chainNames) {
@@ -1154,18 +1149,16 @@ public class HandleSuperBuilder extends JavacAnnotationHandler {
JCMethodDecl md = (JCMethodDecl) def;
String name = md.name.toString();
boolean matches = name.equals("");
- if (isTolerate(type, md))
- continue;
+ if (isTolerate(type, md)) continue;
if (matches && md.params != null && md.params.length() == 1) {
// Cannot use typeMatches() here, because the parameter could be fully-qualified, partially-qualified, or not qualified.
// A string-compare of the last part should work. If it's a false-positive, users could still @Tolerate it.
String typeName = md.params.get(0).getType().toString();
int lastIndexOfDot = typeName.lastIndexOf('.');
if (lastIndexOfDot >= 0) {
- typeName = typeName.substring(lastIndexOfDot+1);
+ typeName = typeName.substring(lastIndexOfDot + 1);
}
- if ((builderClassName+", ?>").equals(typeName))
- return true;
+ if ((builderClassName+", ?>").equals(typeName)) return true;
}
}
}
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
index b9b8a719..f243c56a 100644
--- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -1757,17 +1757,36 @@ public class JavacHandlerUtil {
}
/**
- * Searches the given field node for annotations that are specifically intentioned to be copied to the setter.
+ * Searches the given field node for annotations that are specifically intended to be copied to the getter.
+ *
+ * @param forceCopyJacksonAnnotations If {@code true}, always copy the annotations regardless of lombok configuration key {@code lombok.copyJacksonAnnotationsToAccessors}.
*/
- public static List findCopyableToSetterAnnotations(JavacNode node) {
- return findAnnotationsInList(node, COPY_TO_SETTER_ANNOTATIONS);
+ public static List findCopyableToGetterAnnotations(JavacNode node, boolean forceCopyJacksonAnnotations) {
+ if (!forceCopyJacksonAnnotations) {
+ Boolean copyAnnotations = node.getAst().readConfiguration(ConfigurationKeys.COPY_JACKSON_ANNOTATIONS_TO_ACCESSORS);
+ if (copyAnnotations == null || !copyAnnotations) return List.nil();
+ }
+ return findAnnotationsInList(node, JACKSON_COPY_TO_GETTER_ANNOTATIONS);
}
/**
- * Searches the given field node for annotations that are specifically intentioned to be copied to the builder's singular method.
+ * Searches the given field node for annotations that are specifically intended to be copied to the setter.
+ *
+ * @param forceCopyJacksonAnnotations If {@code true}, always copy the annotations regardless of lombok configuration key {@code lombok.copyJacksonAnnotationsToAccessors}.
+ */
+ public static List findCopyableToSetterAnnotations(JavacNode node, boolean forceCopyJacksonAnnotations) {
+ if (!forceCopyJacksonAnnotations) {
+ Boolean copyAnnotations = node.getAst().readConfiguration(ConfigurationKeys.COPY_JACKSON_ANNOTATIONS_TO_ACCESSORS);
+ if (copyAnnotations == null || !copyAnnotations) return List.nil();
+ }
+ return findAnnotationsInList(node, JACKSON_COPY_TO_SETTER_ANNOTATIONS);
+ }
+
+ /**
+ * Searches the given field node for annotations that are specifically intended to be copied to the builder's singular method.
*/
public static List findCopyableToBuilderSingularSetterAnnotations(JavacNode node) {
- return findAnnotationsInList(node, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS);
+ return findAnnotationsInList(node, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS);
}
/**
diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java
index 938d6e8f..58433d6d 100644
--- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java
+++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2021 The Project Lombok Authors.
+ * Copyright (C) 2015-2025 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
@@ -362,7 +362,7 @@ public class JavacSingularsRecipes {
statements.prepend(JavacHandlerUtil.generateNullCheck(maker, null, data.getPluralName(), builderType, "%s cannot be null"));
}
- List methodAnnotations = copyAnnotations(findCopyableToSetterAnnotations(data.annotation.up()), maker);
+ List methodAnnotations = copyAnnotations(findCopyableToSetterAnnotations(data.annotation.up(), true), maker);
finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, List.of(param), methodAnnotations, access, ignoreNullCollections);
}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java b/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java
index caa94e2f..2d39f8b4 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020-2024 The Project Lombok Authors.
+ * Copyright (C) 2020-2025 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
@@ -78,8 +78,8 @@ public class PatchJavadoc {
}
/**
- * The method javadoc2HTML changed 2014-12 to accept an
- * additional IJavaElement parameter. To support older versions, try to
+ * The method {@code javadoc2HTML} changed 2014-12 to accept an
+ * additional {@code IJavaElement} parameter. To support older versions, try to
* find that one too.
*/
private static class Reflection {
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
index 5b8e3211..a646f954 100644
--- a/src/launch/lombok/launch/ShadowClassLoader.java
+++ b/src/launch/lombok/launch/ShadowClassLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014-2023 The Project Lombok Authors.
+ * Copyright (C) 2014-2025 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
@@ -67,14 +67,14 @@ import java.util.zip.ZipInputStream;
*
* Load order is as follows if at least one override is present:
*
First, if the resource is found in one of the paths stated in the shadow classpath, find that.
- *
Next, ask the parent loader, which is passed during construction of the ShadowClassLoader.
+ *
Next, ask the {@code parent} loader, which is passed during construction of the ShadowClassLoader.
*
Notably, this jar's contents are always skipped! (The idea of the shadow classpath is that this jar only functions as a launcher, not as a source of your actual application).
*
*
* If no overrides are present, the load order is as follows:
*
First, if the resource is found in our own jar (trying ".SCL.sclSuffix" first for any resource request ending in ".class"), return that.
- *
Next, check any jar files other than our own, loading them via this classloader, if they have a file META-INF/ShadowClassLoader that contains a line of text with sclSuffix.
- *
Next, ask the parent loader.
+ *
Next, check any jar files other than our own, loading them via this classloader, if they have a file {@code META-INF/ShadowClassLoader} that contains a line of text with sclSuffix.
+ *
Next, ask the {@code parent} loader.
*
*
* Use ShadowClassLoader to accomplish the following things:
diff --git a/test/transform/resource/after-delombok/JacksonizedAccessors.java b/test/transform/resource/after-delombok/JacksonizedAccessors.java
new file mode 100644
index 00000000..4e16a167
--- /dev/null
+++ b/test/transform/resource/after-delombok/JacksonizedAccessors.java
@@ -0,0 +1,20 @@
+public class JacksonizedAccessors {
+ @com.fasterxml.jackson.annotation.JsonProperty("intValue")
+ private int intValue;
+ @com.fasterxml.jackson.annotation.JsonProperty("intValue")
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public int intValue() {
+ return this.intValue;
+ }
+ /**
+ * @return {@code this}.
+ */
+ @com.fasterxml.jackson.annotation.JsonProperty("intValue")
+ @java.lang.SuppressWarnings("all")
+ @lombok.Generated
+ public JacksonizedAccessors intValue(final int intValue) {
+ this.intValue = intValue;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/test/transform/resource/after-delombok/JacksonBuilderSingular.java b/test/transform/resource/after-delombok/JacksonizedBuilderSingular.java
similarity index 73%
rename from test/transform/resource/after-delombok/JacksonBuilderSingular.java
rename to test/transform/resource/after-delombok/JacksonizedBuilderSingular.java
index 0426b014..fffbcf61 100644
--- a/test/transform/resource/after-delombok/JacksonBuilderSingular.java
+++ b/test/transform/resource/after-delombok/JacksonizedBuilderSingular.java
@@ -5,8 +5,8 @@ import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-@com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonBuilderSingular.JacksonBuilderSingularBuilder.class)
-public class JacksonBuilderSingular {
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder.class)
+public class JacksonizedBuilderSingular {
@JsonAnySetter
private Map any;
@JsonProperty("v_a_l_u_e_s")
@@ -17,7 +17,7 @@ public class JacksonBuilderSingular {
private ImmutableList guavaValues;
@java.lang.SuppressWarnings("all")
@lombok.Generated
- JacksonBuilderSingular(final Map any, final List values, final ImmutableMap guavaAny, final ImmutableList guavaValues) {
+ JacksonizedBuilderSingular(final Map any, final List values, final ImmutableMap guavaAny, final ImmutableList guavaValues) {
this.any = any;
this.values = values;
this.guavaAny = guavaAny;
@@ -26,7 +26,7 @@ public class JacksonBuilderSingular {
@java.lang.SuppressWarnings("all")
@lombok.Generated
@com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "", buildMethodName = "build")
- public static class JacksonBuilderSingularBuilder {
+ public static class JacksonizedBuilderSingularBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList any$key;
@@ -44,12 +44,12 @@ public class JacksonBuilderSingular {
private com.google.common.collect.ImmutableList.Builder guavaValues;
@java.lang.SuppressWarnings("all")
@lombok.Generated
- JacksonBuilderSingularBuilder() {
+ JacksonizedBuilderSingularBuilder() {
}
@JsonAnySetter
@java.lang.SuppressWarnings("all")
@lombok.Generated
- public JacksonBuilderSingular.JacksonBuilderSingularBuilder any(final String anyKey, final Object anyValue) {
+ public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder any(final String anyKey, final Object anyValue) {
if (this.any$key == null) {
this.any$key = new java.util.ArrayList();
this.any$value = new java.util.ArrayList