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 true any 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 true any 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: *

* 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.
    • @@ -54,9 +63,9 @@ import lombok.experimental.SuperBuilder; *
    • 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(); @@ -60,7 +60,7 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder any(final java.util.Map any) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder any(final java.util.Map any) { if (any == null) { throw new java.lang.NullPointerException("any cannot be null"); } @@ -76,7 +76,7 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder clearAny() { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearAny() { if (this.any$key != null) { this.any$key.clear(); this.any$value.clear(); @@ -85,7 +85,7 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder value(final String value) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder value(final String value) { if (this.values == null) this.values = new java.util.ArrayList(); this.values.add(value); return this; @@ -93,7 +93,7 @@ public class JacksonBuilderSingular { @JsonProperty("v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder values(final java.util.Collection values) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder values(final java.util.Collection values) { if (values == null) { throw new java.lang.NullPointerException("values cannot be null"); } @@ -103,21 +103,21 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder clearValues() { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearValues() { if (this.values != null) this.values.clear(); return this; } @JsonAnySetter @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaAny(final String key, final Object value) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaAny(final String key, final Object value) { if (this.guavaAny == null) this.guavaAny = com.google.common.collect.ImmutableMap.builder(); this.guavaAny.put(key, value); return this; } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaAny(final java.util.Map guavaAny) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaAny(final java.util.Map guavaAny) { if (guavaAny == null) { throw new java.lang.NullPointerException("guavaAny cannot be null"); } @@ -127,13 +127,13 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder clearGuavaAny() { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearGuavaAny() { this.guavaAny = null; return this; } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaValue(final String guavaValue) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaValue(final String guavaValue) { if (this.guavaValues == null) this.guavaValues = com.google.common.collect.ImmutableList.builder(); this.guavaValues.add(guavaValue); return this; @@ -141,7 +141,7 @@ public class JacksonBuilderSingular { @JsonProperty("guava_v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaValues(final java.lang.Iterable guavaValues) { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaValues(final java.lang.Iterable guavaValues) { if (guavaValues == null) { throw new java.lang.NullPointerException("guavaValues cannot be null"); } @@ -151,13 +151,13 @@ public class JacksonBuilderSingular { } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular.JacksonBuilderSingularBuilder clearGuavaValues() { + public JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearGuavaValues() { this.guavaValues = null; return this; } @java.lang.SuppressWarnings("all") @lombok.Generated - public JacksonBuilderSingular build() { + public JacksonizedBuilderSingular build() { java.util.Map any; switch (this.any$key == null ? 0 : this.any$key.size()) { case 0: @@ -184,18 +184,18 @@ public class JacksonBuilderSingular { } com.google.common.collect.ImmutableMap guavaAny = this.guavaAny == null ? com.google.common.collect.ImmutableMap.of() : this.guavaAny.build(); com.google.common.collect.ImmutableList guavaValues = this.guavaValues == null ? com.google.common.collect.ImmutableList.of() : this.guavaValues.build(); - return new JacksonBuilderSingular(any, values, guavaAny, guavaValues); + return new JacksonizedBuilderSingular(any, values, guavaAny, guavaValues); } @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated public java.lang.String toString() { - return "JacksonBuilderSingular.JacksonBuilderSingularBuilder(any$key=" + this.any$key + ", any$value=" + this.any$value + ", values=" + this.values + ", guavaAny=" + this.guavaAny + ", guavaValues=" + this.guavaValues + ")"; + return "JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder(any$key=" + this.any$key + ", any$value=" + this.any$value + ", values=" + this.values + ", guavaAny=" + this.guavaAny + ", guavaValues=" + this.guavaValues + ")"; } } @java.lang.SuppressWarnings("all") @lombok.Generated - public static JacksonBuilderSingular.JacksonBuilderSingularBuilder builder() { - return new JacksonBuilderSingular.JacksonBuilderSingularBuilder(); + public static JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder builder() { + return new JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder(); } -} +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/JacksonizedAccessors.java b/test/transform/resource/after-ecj/JacksonizedAccessors.java new file mode 100644 index 00000000..dd15a397 --- /dev/null +++ b/test/transform/resource/after-ecj/JacksonizedAccessors.java @@ -0,0 +1,16 @@ +public @lombok.extern.jackson.Jacksonized @lombok.experimental.Accessors(fluent = true) @lombok.Getter @lombok.Setter class JacksonizedAccessors { + private @com.fasterxml.jackson.annotation.JsonProperty("intValue") int intValue; + public JacksonizedAccessors() { + super(); + } + public @com.fasterxml.jackson.annotation.JsonProperty("intValue") @java.lang.SuppressWarnings("all") @lombok.Generated int intValue() { + return this.intValue; + } + /** + * @return {@code this}. + */ + public @com.fasterxml.jackson.annotation.JsonProperty("intValue") @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedAccessors intValue(final int intValue) { + this.intValue = intValue; + return this; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/JacksonBuilderSingular.java b/test/transform/resource/after-ecj/JacksonizedBuilderSingular.java similarity index 74% rename from test/transform/resource/after-ecj/JacksonBuilderSingular.java rename to test/transform/resource/after-ecj/JacksonizedBuilderSingular.java index 309a09ac..29a28076 100644 --- a/test/transform/resource/after-ecj/JacksonBuilderSingular.java +++ b/test/transform/resource/after-ecj/JacksonizedBuilderSingular.java @@ -7,17 +7,17 @@ import com.google.common.collect.ImmutableMap; import lombok.Builder; import lombok.Singular; import lombok.extern.jackson.Jacksonized; -public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonBuilderSingular.JacksonBuilderSingularBuilder.class) class JacksonBuilderSingular { - public static @java.lang.SuppressWarnings("all") @lombok.Generated @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "",buildMethodName = "build") class JacksonBuilderSingularBuilder { +public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder.class) class JacksonizedBuilderSingular { + public static @java.lang.SuppressWarnings("all") @lombok.Generated @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "",buildMethodName = "build") class JacksonizedBuilderSingularBuilder { private @java.lang.SuppressWarnings("all") @lombok.Generated java.util.ArrayList any$key; private @java.lang.SuppressWarnings("all") @lombok.Generated java.util.ArrayList any$value; private @java.lang.SuppressWarnings("all") @lombok.Generated java.util.ArrayList values; private @java.lang.SuppressWarnings("all") @lombok.Generated com.google.common.collect.ImmutableMap.Builder guavaAny; private @java.lang.SuppressWarnings("all") @lombok.Generated com.google.common.collect.ImmutableList.Builder guavaValues; - @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingularBuilder() { + @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingularBuilder() { super(); } - public @JsonAnySetter @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder any(final String anyKey, final Object anyValue) { + public @JsonAnySetter @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder any(final String anyKey, final Object anyValue) { if ((this.any$key == null)) { this.any$key = new java.util.ArrayList(); @@ -27,7 +27,7 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese this.any$value.add(anyValue); return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder any(final java.util.Map any) { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder any(final java.util.Map any) { if ((any == null)) { throw new java.lang.NullPointerException("any cannot be null"); @@ -44,7 +44,7 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese } return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder clearAny() { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearAny() { if ((this.any$key != null)) { this.any$key.clear(); @@ -52,13 +52,13 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese } return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder value(final String value) { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder value(final String value) { if ((this.values == null)) this.values = new java.util.ArrayList(); this.values.add(value); return this; } - public @JsonProperty("v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder values(final java.util.Collection values) { + public @JsonProperty("v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder values(final java.util.Collection values) { if ((values == null)) { throw new java.lang.NullPointerException("values cannot be null"); @@ -68,18 +68,18 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese this.values.addAll(values); return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder clearValues() { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearValues() { if ((this.values != null)) this.values.clear(); return this; } - public @JsonAnySetter @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaAny(final String key, final Object value) { + public @JsonAnySetter @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaAny(final String key, final Object value) { if ((this.guavaAny == null)) this.guavaAny = com.google.common.collect.ImmutableMap.builder(); this.guavaAny.put(key, value); return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaAny(final java.util.Map guavaAny) { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaAny(final java.util.Map guavaAny) { if ((guavaAny == null)) { throw new java.lang.NullPointerException("guavaAny cannot be null"); @@ -89,17 +89,17 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese this.guavaAny.putAll(guavaAny); return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder clearGuavaAny() { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearGuavaAny() { this.guavaAny = null; return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaValue(final String guavaValue) { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaValue(final String guavaValue) { if ((this.guavaValues == null)) this.guavaValues = com.google.common.collect.ImmutableList.builder(); this.guavaValues.add(guavaValue); return this; } - public @JsonProperty("guava_v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder guavaValues(final java.lang.Iterable guavaValues) { + public @JsonProperty("guava_v_a_l_u_e_s") @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder guavaValues(final java.lang.Iterable guavaValues) { if ((guavaValues == null)) { throw new java.lang.NullPointerException("guavaValues cannot be null"); @@ -109,11 +109,11 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese this.guavaValues.addAll(guavaValues); return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder clearGuavaValues() { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder clearGuavaValues() { this.guavaValues = null; return this; } - public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular build() { + public @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular build() { java.util.Map any; switch (((this.any$key == null) ? 0 : this.any$key.size())) { case 0 : @@ -141,24 +141,24 @@ public @Jacksonized @Builder @com.fasterxml.jackson.databind.annotation.JsonDese } com.google.common.collect.ImmutableMap guavaAny = ((this.guavaAny == null) ? com.google.common.collect.ImmutableMap.of() : this.guavaAny.build()); com.google.common.collect.ImmutableList guavaValues = ((this.guavaValues == null) ? com.google.common.collect.ImmutableList.of() : this.guavaValues.build()); - return new JacksonBuilderSingular(any, values, guavaAny, guavaValues); + return new JacksonizedBuilderSingular(any, values, guavaAny, guavaValues); } public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { - return (((((((((("JacksonBuilderSingular.JacksonBuilderSingularBuilder(any$key=" + this.any$key) + ", any$value=") + this.any$value) + ", values=") + this.values) + ", guavaAny=") + this.guavaAny) + ", guavaValues=") + this.guavaValues) + ")"); + return (((((((((("JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder(any$key=" + this.any$key) + ", any$value=") + this.any$value) + ", values=") + this.values) + ", guavaAny=") + this.guavaAny) + ", guavaValues=") + this.guavaValues) + ")"); } } private @JsonAnySetter @Singular("any") Map any; private @JsonProperty("v_a_l_u_e_s") @Singular List values; private @JsonAnySetter @Singular("guavaAny") ImmutableMap guavaAny; private @JsonProperty("guava_v_a_l_u_e_s") @Singular ImmutableList guavaValues; - @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular(final Map any, final List values, final ImmutableMap guavaAny, final ImmutableList guavaValues) { + @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular(final Map any, final List values, final ImmutableMap guavaAny, final ImmutableList guavaValues) { super(); this.any = any; this.values = values; this.guavaAny = guavaAny; this.guavaValues = guavaValues; } - public static @java.lang.SuppressWarnings("all") @lombok.Generated JacksonBuilderSingular.JacksonBuilderSingularBuilder builder() { - return new JacksonBuilderSingular.JacksonBuilderSingularBuilder(); + public static @java.lang.SuppressWarnings("all") @lombok.Generated JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder builder() { + return new JacksonizedBuilderSingular.JacksonizedBuilderSingularBuilder(); } -} +} \ No newline at end of file diff --git a/test/transform/resource/before/JacksonJsonProperty.java b/test/transform/resource/before/JacksonJsonProperty.java index c0bccfae..716298f2 100644 --- a/test/transform/resource/before/JacksonJsonProperty.java +++ b/test/transform/resource/before/JacksonJsonProperty.java @@ -1,3 +1,4 @@ +//CONF: lombok.copyJacksonAnnotationsToAccessors = true //version 8: Jackson deps are at least Java7+. import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; diff --git a/test/transform/resource/before/JacksonizedAccessors.java b/test/transform/resource/before/JacksonizedAccessors.java new file mode 100644 index 00000000..0fe01684 --- /dev/null +++ b/test/transform/resource/before/JacksonizedAccessors.java @@ -0,0 +1,7 @@ +@lombok.extern.jackson.Jacksonized +@lombok.experimental.Accessors(fluent = true) +@lombok.Getter +@lombok.Setter +public class JacksonizedAccessors { + private int intValue; +} \ No newline at end of file diff --git a/test/transform/resource/before/JacksonBuilderSingular.java b/test/transform/resource/before/JacksonizedBuilderSingular.java similarity index 94% rename from test/transform/resource/before/JacksonBuilderSingular.java rename to test/transform/resource/before/JacksonizedBuilderSingular.java index 20d6979a..a34ab9c4 100644 --- a/test/transform/resource/before/JacksonBuilderSingular.java +++ b/test/transform/resource/before/JacksonizedBuilderSingular.java @@ -13,7 +13,7 @@ import lombok.extern.jackson.Jacksonized; @Jacksonized @Builder -public class JacksonBuilderSingular { +public class JacksonizedBuilderSingular { @JsonAnySetter @Singular("any") private Map any;