diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 9109c65573..4e66ca026a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ -custom: https://projectlombok.org/order-license-info patreon: lombok tidelift: maven/org.projectlombok:lombok github: projectlombok diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml deleted file mode 100644 index 2022b3a6b6..0000000000 --- a/.github/workflows/ant.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: Tests - -on: - push: - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up JDK 11 - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 11 - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ivyCache - lib - key: ivy-${{ hashFiles('**/ivy.xml') }} - restore-keys: | - ivy- - - - name: Build with Ant - run: ant -noinput dist - - - uses: actions/upload-artifact@v4 - with: - name: lombok.jar - path: dist/lombok.jar - - - test-javac: - runs-on: ubuntu-latest - needs: build - env: - EA_JDK: 22 - strategy: - matrix: - jdk: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] - goal: [javacCurrent] - include: - - jdk: 11 - goal: javac6 - - jdk: 11 - goal: javac8 - fail-fast: false - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up JDK ${{ matrix.jdk }} - if: ${{ matrix.jdk < env.EA_JDK }} - uses: actions/setup-java@v4 - with: - java-version: ${{ matrix.jdk }} - distribution: 'zulu' - - - name: Set up JDK ${{ matrix.jdk }} Early Access release - if: ${{ matrix.jdk >= env.EA_JDK }} - uses: oracle-actions/setup-java@v1 - with: - website: jdk.java.net - release: ${{ matrix.jdk }} - version: latest - - - name: Install Ant - env: - ANT_VERSION: 1.10.14 - run: | - wget https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.zip - unzip apache-ant-$ANT_VERSION-bin.zip -d "${HOME}" - echo "ANT_HOME=${HOME}/apache-ant-$ANT_VERSION" >> $GITHUB_ENV - echo "${HOME}/apache-ant-$ANT_VERSION/bin" >> $GITHUB_PATH - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ivyCache - lib - key: ivy-${{ hashFiles('**/ivy.xml') }} - restore-keys: | - ivy- - - - name: Run tests - run: ant -noinput test.${{ matrix.goal }} - - test-eclipse: - runs-on: ubuntu-latest - needs: build - strategy: - matrix: - version: - - eclipse-oxygen - - eclipse-202006 - - eclipse-202006-jdk8 - - eclipse-202212 - - eclipse-202309 - - eclipse-202312 - - eclipse-I-build - - eclipse-oxygen-full - - eclipse-2022-03-full - - eclipse-2023-09-full - - eclipse-2023-12-full - - eclipse-I-build-full - - ecj11 - - ecj14 - - ecj16 - - ecj19 - fail-fast: false - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'zulu' - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ivyCache - lib - key: ivy-${{ hashFiles('**/ivy.xml') }} - restore-keys: | - ivy- - - - name: Cache base testenv - if: ${{ !endsWith(matrix.version, 'full') }} - uses: actions/cache@v4 - with: - path: | - testenv - key: base-testenv-${{ hashFiles('**/setup.ant.xml') }} - - - name: Cache full testenv - if: ${{ endsWith(matrix.version, 'full') }} - uses: actions/cache@v4 - with: - path: | - testenv - key: ${{ matrix.version }}-testenv-${{ hashFiles('**/setup.ant.xml') }} - - - name: Build with Ant - run: xvfb-run ant -noinput dist test.${{ matrix.version }} - - docker-integration-test: - runs-on: ubuntu-latest - needs: build - strategy: - matrix: - jdk: [8, 11, 17, 21, 22] - tool: - - {name: "maven", cmd: "mvn compile"} - - {name: "gradle", cmd: "gradle assemble"} - - {name: "ant", cmd: "ant dist"} - - {name: "bazel", cmd: "bazel build //:ProjectRunner"} - exclude: - - {tool: {name: "gradle", cmd: "gradle assemble"}, jdk: 22} - fail-fast: false - env: - IMAGE_NAME: lombok-${{ matrix.tool.name }}-jdk${{ matrix.jdk }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v4 - with: - name: lombok.jar - - - name: Build container - working-directory: ./docker - run: docker build --build-arg jdk=${{ matrix.jdk }} -t $IMAGE_NAME -f ${{ matrix.tool.name }}/Dockerfile . - - - name: Compile in container - run: docker run --entrypoint="" -v $(pwd)/lombok.jar:/workspace/lombok.jar $IMAGE_NAME /bin/bash -c "cd classpath; ${{ matrix.tool.cmd }}" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index b525b9a26c..0000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '18 19 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java', 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - # No, you cannot just build lombok - ###- name: Autobuild - ### uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - run: | - ant dist - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/AUTHORS b/AUTHORS index a8c9bcc459..4a6ef2fdea 100755 --- a/AUTHORS +++ b/AUTHORS @@ -4,6 +4,7 @@ Adam Juraszek Aleksandr Zhelezniak Amine Touzani Andre Brait +Anshuman Mishra Bulgakov Alexander Caleb Brinkman Christian Nüssgens @@ -24,7 +25,9 @@ Jappe van der Hel John Paul Taylor II Karthik kathari <44122128+varkart@users.noreply.github.com> Kevin Chirls +Lars Uffmann Liu DongMiao +Liam Pace Luan Nico Maarten Mulders Manu Sridharan @@ -36,6 +39,7 @@ Mateusz Matela Michael Dardis Michael Ernst Michiel Verheul +MoonFruit Ole Ludwig Pascal Bihler Peter Grant @@ -43,6 +47,7 @@ Philipp Eichhorn Philippe Charles Pim van der Loos Rabea Gransberger +Rafael Alemañ Cabrera <2362816+ralemanc@users.noreply.github.com> Raul Wißfeld Reinier Zwitserloot Rob Stryker diff --git a/README.md b/README.md index fea9ecc9e2..afc38a7237 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Project Lombok -Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. +**Project Lombok** is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, automate your logging variables, and much more. See [LICENSE] for the Project Lombok license. diff --git a/buildScripts/create-eclipse-project.ant.xml b/buildScripts/create-eclipse-project.ant.xml index f9c45bc644..929b542122 100644 --- a/buildScripts/create-eclipse-project.ant.xml +++ b/buildScripts/create-eclipse-project.ant.xml @@ -98,9 +98,9 @@ This buildfile is part of projectlombok.org. It creates the infrastructure neede - + - + diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.48.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.52.xml similarity index 87% rename from buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.48.xml rename to buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.52.xml index 59eda49314..61905395cb 100644 --- a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.48.xml +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.52.xml @@ -1,5 +1,5 @@ - + @@ -9,6 +9,6 @@ - + diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml new file mode 100644 index 0000000000..5779f3a3ce --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.56.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.56.xml new file mode 100644 index 0000000000..45f5752b98 --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.56.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.58.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.58.xml new file mode 100644 index 0000000000..875a8d1f95 --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.58.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.60.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.60.xml new file mode 100644 index 0000000000..415e1ed60c --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.60.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index aa2643ed90..6dbcfa9ba7 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -30,15 +30,15 @@ - - - + + + - + @@ -76,7 +76,7 @@ - + @@ -109,43 +109,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buildScripts/maven.ant.xml b/buildScripts/maven.ant.xml index 52b395c00b..cc38c849d0 100644 --- a/buildScripts/maven.ant.xml +++ b/buildScripts/maven.ant.xml @@ -50,7 +50,7 @@ This buildfile is part of projectlombok.org. It makes maven-compatible repositor - + @@ -67,9 +67,9 @@ This buildfile is part of projectlombok.org. It makes maven-compatible repositor - + - Your lombok clone does not include the OSSRH deployment keys. Contact the core maintainers for these keys; + Your lombok clone does not include the central.sonatype.org deployment keys. Contact the core maintainers for these keys; place them in ${gpg.keyrings} to continue. @@ -77,19 +77,20 @@ This buildfile is part of projectlombok.org. It makes maven-compatible repositor - - Your lombok clone does not include the OSSRH (sonatype maven central) password, needed to upload and deploy to maven central. Contact the core maintainers. + + Your lombok clone does not include an central.sonatype.org authToken, needed to upload and deploy to maven central. Contact the core maintainers. - - + + + - + @@ -98,7 +99,7 @@ This buildfile is part of projectlombok.org. It makes maven-compatible repositor - + @@ -108,18 +109,26 @@ This buildfile is part of projectlombok.org. It makes maven-compatible repositor - + + + + + + + + + + + + + + + + - + - - - - - - - - The artifact has been published to staging. Now go to https://oss.sonatype.org/ and log in as Reinier, then doublecheck if all is well and 'release' it. + An artifact ready to upload to central.sonatype.com is available at: ${maven.publish.bundlezip} diff --git a/buildScripts/setup.ant.xml b/buildScripts/setup.ant.xml index 4f73f956dd..9353ee2a69 100644 --- a/buildScripts/setup.ant.xml +++ b/buildScripts/setup.ant.xml @@ -160,16 +160,20 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - + - - + + - - + + + + + + @@ -205,6 +209,8 @@ This buildfile is part of projectlombok.org. It sets up the build itself. + + @@ -248,13 +254,12 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - - + + - diff --git a/buildScripts/tests.ant.xml b/buildScripts/tests.ant.xml index ade6fbffe1..82292dc059 100644 --- a/buildScripts/tests.ant.xml +++ b/buildScripts/tests.ant.xml @@ -1,5 +1,5 @@ + + + + + + - - + + + + + + + + @@ -194,11 +207,6 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - - - - @@ -213,14 +221,19 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - - + + + + + + + + - - - + + + @@ -238,6 +251,7 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn + @@ -283,16 +297,16 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - + + - - + + - - + + @@ -340,6 +354,6 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - + + diff --git a/buildScripts/vm-finder.ant.xml b/buildScripts/vm-finder.ant.xml index 2a33b2b88a..94682fa4c4 100644 --- a/buildScripts/vm-finder.ant.xml +++ b/buildScripts/vm-finder.ant.xml @@ -62,7 +62,7 @@ and rerun the build; this build is capable of finding VMs automatically on many - + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index a3b3a48add..7375d3103f 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,13 +1,65 @@ Lombok Changelog ---------------- -### v1.18.31 "Edgy Guinea Pig" +### v1.18.47 "Edgy Guinea Pig" +* BUGFIX: `@SneakyThrows` usage on JDK26 no longer results in class files that require `lombok.jar` to be on the runtime classpath (which should not be neccessary). [#4040](https://github.com/projectlombok/lombok/issues/4022). +* PROMOTION: `@SuperBuilder` has been promoted to the main package. Otherwise, no changes have been made to the annotation. The old experimental annotation will remain for a few versions, at which point it will be marked as a deprecated annotation. Eventually it'll be removed. If you had `lombok.config` configuration for this annotation, the configuration keys for this feature have been renamed. +* OLD-CRUFT: `lombok.experimental.Wither` and `lombok.Delegate` are deprecated remnants; these features were moved (to respectively `lombok.With` and `lombok.experimental.Delegate` over 5 years ago. They are now removed entirely. If your project is dependent on an older version of lombok which still has those; fret not, lombok still processes these annotations. It just no longer includes them in the jar. + +### v1.18.46 (April 22nd, 2026) +* PLATFORM: JDK26 support added [#4019](https://github.com/projectlombok/lombok/issues/4019). +* PLATFORM: Spring Tools Suite 5 supported [#3985](https://github.com/projectlombok/lombok/issues/3985). +* BUGFIX: `@Jacksonized` no longer stops generating `@JsonProperty` once an explicit `@JsonIgnore` annotations is encountered [#4022](https://github.com/projectlombok/lombok/issues/4022). +* BUGFIX: In eclipse, mixing `@Jacksonized` and `fluent = true` no longer causes the error `com.fasterxml.jackson.annotation.JsonProperty is not a repeatable annotation interface`. [#3934](https://github.com/projectlombok/lombok/issues/3934). +* BUGFIX: Some finishing touches for v1.18.44's support of Jackson3 [#4004](https://github.com/projectlombok/lombok/issues/4004). + +### v1.18.44 (March 11th, 2026) +* FEATURE: `@Jacksonized` now supports both Jackson2 and Jackson3; you'll get a warning until you configure which one (or even both!) you want lombok to generate. [#3950](https://github.com/projectlombok/lombok/issues/3950). +* BUGFIX: On JDK25, `val` and `@ExtensionMethod` could sometimes cause erroneous errors (in that you see errors but compilation succeeds anyway) using javac. [#3947](https://github.com/projectlombok/lombok/issues/3947). +* BUGFIX: `@Jacksonized` + fields marked `transient` would result in those transient fields being serialised which is surprising (and thus undesired) behaviour. [#3936](https://github.com/projectlombok/lombok/issues/3936). + +### v1.18.42 (September 18th, 2025) +* FEATURE: All the various `@Log` annotations now allow you to change their access level (they still default to `private`). [#2280](https://github.com/projectlombok/lombok/issues/2280). Thanks to new contributor Liam Pace! +* BUGFIX: Javadoc parsing was broken in Netbeans and ErrorProne for JDK25 [#3940](https://github.com/projectlombok/lombok/issues/3940). + +### v1.18.40 (September 4th, 2025) +* PLATFORM: JDK25 support added [#3859](https://github.com/projectlombok/lombok/issues/3859). +* BUGFIX: Recent versions of eclipse (or the eclipse-based java lang server for VSCode) caused `java.lang.IllegalArgumentException: Document does not match the AST`. [Issue #3886](https://github.com/projectlombok/lombok/issues/3886). +* PERFORMANCE: `@ExtensionMethod` is now significantly faster [Issue #3866](https://github.com/projectlombok/lombok/issues/3866). +* BUGFIX: the command line `config` tool would emit incorrect output for nullity annotations. [Issue #3931](https://github.com/projectlombok/lombok/issues/3931). +* FEATURE: `@Jacksonized @Accessors(fluent=true)` automatically creates the relevant annotations such that Jackson correctly identifies fluent accessors. [Issue #3265](https://github.com/projectlombok/lombok/issues/3265), [Issue #3270](https://github.com/projectlombok/lombok/issues/3270). +* 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. +* FEATURE: Lombok's nullity annotation now supports [JSpecify](https://jspecify.dev) out of the box, using [config key](https://projectlombok.org/features/configuration) `jspecify`. +* BUGFIX: Recent eclipse releases would get you 'negative length' error. The bug had always been in lombok but didn't matter until recent releases. [Issue #3823](https://github.com/projectlombok/lombok/issues/3823). +* BUGFIX: The 'extract local variable' refactor script of VSCode wouldn't replace all occurrences if run on a method call to a lombok generated method. [Issue #3783](https://github.com/projectlombok/lombok/issues/3783). + +### v1.18.36 (November 15th, 2024) +* PLATFORM: JDK23 support added. +* BUGFIX: Eclipse projects using the `com.pro-crafting.tools:jasperreports-maven-plugin` will now compile. + +### v1.18.34 (June 28th, 2024) +* PLATFORM: Added support for Eclipse 2024-06; you'd get some `NoSuchMethodError` traces in your logs if using `@Builder` or `@Singular` prior to this fix. [Issue #3638](https://github.com/projectlombok/lombok/issues/3638). +* IMPROBABLE BREAKING CHANGE: Lombok now adds `@lombok.Generated` by default to methods and types it generates. This may result in accidentally increasing your test coverage percentage. [Issue #3667](https://github.com/projectlombok/lombok/issues/3667). +* IMPROBABLE BREAKING CHANGE: When `lombok.config` contains `lombok.onX.flagUsage = WARNING`, from now on warnings will actually be generated if onX is used.[Issue #2848](https://github.com/projectlombok/lombok/issues/2848) +* BUGFIX: When `@SuperBuilder` was used on a type with an generic array type, it would error `wrong number of type arguments`. [Issue #3694](https://github.com/projectlombok/lombok/issues/3694). +* FEATURE: Lombok generates javadoc for you for most of the methods it adds; with this release, javadoc is also added to generated constructors. [Issue #933](https://github.com/projectlombok/lombok/issues/933). + +### v1.18.32 (March 20th, 2024) +* PLATFORM: Initial JDK22 support added. +* PLAFTORM Added support for Eclipse 2024-03. [Issue #3620](https://github.com/projectlombok/lombok/issues/3620). * PLATFORM: Added support for recent versions of eclipse (released Q4 2023 or later or so) which would cause failures in the eclipse logs such as `java.lang.NoSuchMethodError: 'java.lang.StringBuffer org.eclipse.jdt…`. [Issue #3564](https://github.com/projectlombok/lombok/issues/3564). * FEATURE: `@Locked` has been introduced. Like `@Synchronized` but with `java.util.concurrent.locks` locks instead of the `synchronized` primitive. Thanks, Pim van der Loos for the PR! [Issue #3506](https://github.com/projectlombok/lombok/issues/3506). * NECROMANCY: Inlining a generated getter in eclipse would result in eclipse incorrectly replacing calls with `@Getter` instead of the actual field's name. [Issue #562](https://github.com/projectlombok/lombok/issues/562). This issue is almost old enough to drink. Points for dedication go to Rawi for fixing this one. +* BUGFIX: When `@SuperBuilder` was used on a type with an annotated generic type, it would error `wrong number of type arguments`. [Issue #3592](https://github.com/projectlombok/lombok/issues/3592). +* BUGFIX: It was possible to create an infinite build loop using `@ExtensionMethod`. [Issue #3225](https://github.com/projectlombok/lombok/issues/3225). +* BUGFIX: Using `@Getter(lazy=true)` would fail if the expression contained a variable called `value`. [Issue #2917](https://github.com/projectlombok/lombok/issues/2917). * BUGFIX: Many lombok features wouldn't work properly on records contained within an outer type unless you explicitly marked it `static`. [Issue #3497](https://github.com/projectlombok/lombok/issues/3497) [Issue #3559](https://github.com/projectlombok/lombok/issues/3559). * BUGFIX: Eclipse projects using the `com.pro-crafting.tools:jasperreports-plugin` will now compile. * BUGFIX: `@FieldNameConstants` now works when generated fields are involved. [Issue #3529](https://github.com/projectlombok/lombok/issues/3529). +* IMPROBABLE BREAKING CHANGE: For JSpecify, the package name changed from `org.jspecify.nullness` to `org.jspecify.annotations`, which might lead to a different null analysis. [Issue #3608](https://github.com/projectlombok/lombok/pull/3608). ### v1.18.30 (September 20th, 2023) * PLATFORM: Initial JDK21 support added. [Issue #3393](https://github.com/projectlombok/lombok/issues/3393). @@ -30,7 +82,7 @@ Lombok Changelog ### v1.18.26 (Feb 3rd, 2023) * PLATFORM: JDK19 support added. [Issue #3264](https://github.com/projectlombok/lombok/issues/3264). -* BUGFIX: Using the refactor script: "Rename field" in a `@(Super)Builder`-marked file in eclipse or VSCode would cause issues. [Issue #3181] (https://github.com/projectlombok/lombok/issues/3181). +* BUGFIX: Using the refactor script: "Rename field" in a `@(Super)Builder`-marked file in eclipse or VSCode would cause issues. [Issue #3181](https://github.com/projectlombok/lombok/issues/3181). * BUGFIX: Using `val` together with any call to a method that explicitly resolves to a default impl in an interface didn't work in javac. [Issue #3242](https://github.com/projectlombok/lombok/issues/3242). ### v1.18.24 (April 18th, 2022) diff --git a/docker/ant/files/jdk-22/classpath/build.xml b/docker/ant/files/jdk-25/classpath/build.xml similarity index 97% rename from docker/ant/files/jdk-22/classpath/build.xml rename to docker/ant/files/jdk-25/classpath/build.xml index 07e5885674..2bb486a85f 100644 --- a/docker/ant/files/jdk-22/classpath/build.xml +++ b/docker/ant/files/jdk-25/classpath/build.xml @@ -20,6 +20,7 @@ + diff --git a/docker/ant/files/jdk-22/modules/build.xml b/docker/ant/files/jdk-25/modules/build.xml similarity index 97% rename from docker/ant/files/jdk-22/modules/build.xml rename to docker/ant/files/jdk-25/modules/build.xml index e1f9355020..13f661acb7 100644 --- a/docker/ant/files/jdk-22/modules/build.xml +++ b/docker/ant/files/jdk-25/modules/build.xml @@ -20,6 +20,7 @@ + diff --git a/docker/gradle/Dockerfile b/docker/gradle/Dockerfile index b83f7f0e13..de6256615e 100644 --- a/docker/gradle/Dockerfile +++ b/docker/gradle/Dockerfile @@ -7,9 +7,9 @@ RUN provision/jdk/java-${jdk}.sh ARG lombokjar=lombok.jar ADD https://projectlombok.org/downloads/${lombokjar} /lombok.jar -ARG gradle=8.5 -ADD provision/gradle/gradle-${gradle}.sh provision/gradle/gradle-${gradle}.sh -RUN provision/gradle/gradle-${gradle}.sh +ARG gradle=8.10.2 +ADD provision/gradle/gradle.sh provision/gradle/gradle.sh +RUN provision/gradle/gradle.sh ${gradle} FROM ubuntu:22.04 diff --git a/docker/gradle/files/classpath/.gitignore b/docker/gradle/files/classpath/.gitignore new file mode 100644 index 0000000000..a86f5fa160 --- /dev/null +++ b/docker/gradle/files/classpath/.gitignore @@ -0,0 +1,5 @@ +build/ +.gradle/ +gradle/ +gradlew +gradlew.bat \ No newline at end of file diff --git a/docker/maven/files/jdk-22/classpath/pom.xml b/docker/maven/files/jdk-25/classpath/pom.xml similarity index 96% rename from docker/maven/files/jdk-22/classpath/pom.xml rename to docker/maven/files/jdk-25/classpath/pom.xml index 316a3b8f10..b96de94f6d 100644 --- a/docker/maven/files/jdk-22/classpath/pom.xml +++ b/docker/maven/files/jdk-25/classpath/pom.xml @@ -7,7 +7,7 @@ UTF-8 - 22 + 25 @@ -22,6 +22,7 @@ true true true + full -Werror -Xlint:all diff --git a/docker/maven/files/jdk-22/modules/pom.xml b/docker/maven/files/jdk-25/modules/pom.xml similarity index 98% rename from docker/maven/files/jdk-22/modules/pom.xml rename to docker/maven/files/jdk-25/modules/pom.xml index 95c3a86e48..f822c186ec 100644 --- a/docker/maven/files/jdk-22/modules/pom.xml +++ b/docker/maven/files/jdk-25/modules/pom.xml @@ -7,7 +7,7 @@ UTF-8 - 22 + 25 diff --git a/docker/provision/gradle/gradle-6.8.3.sh b/docker/provision/gradle/gradle-6.8.3.sh deleted file mode 100755 index 62ab0f629d..0000000000 --- a/docker/provision/gradle/gradle-6.8.3.sh +++ /dev/null @@ -1,4 +0,0 @@ -apt-get update && apt-get install -y wget unzip -wget https://services.gradle.org/distributions/gradle-6.8.3-bin.zip -O gradle.zip -mkdir /opt/gradle && unzip -d /opt/gradle gradle.zip -mv /opt/gradle/gradle-6.8.3 /opt/gradle/gradle diff --git a/docker/provision/gradle/gradle-7.6.1.sh b/docker/provision/gradle/gradle-7.6.1.sh deleted file mode 100755 index d56d318a10..0000000000 --- a/docker/provision/gradle/gradle-7.6.1.sh +++ /dev/null @@ -1,4 +0,0 @@ -apt-get update && apt-get install -y wget unzip -wget https://services.gradle.org/distributions/gradle-7.6.1-bin.zip -O gradle.zip -mkdir /opt/gradle && unzip -d /opt/gradle gradle.zip -mv /opt/gradle/gradle-7.6.1 /opt/gradle/gradle diff --git a/docker/provision/gradle/gradle-8.3.sh b/docker/provision/gradle/gradle-8.3.sh deleted file mode 100755 index 507087d470..0000000000 --- a/docker/provision/gradle/gradle-8.3.sh +++ /dev/null @@ -1,4 +0,0 @@ -apt-get update && apt-get install -y wget unzip -wget https://services.gradle.org/distributions/gradle-8.3-bin.zip -O gradle.zip -mkdir /opt/gradle && unzip -d /opt/gradle gradle.zip -mv /opt/gradle/gradle-8.3 /opt/gradle/gradle diff --git a/docker/provision/gradle/gradle-8.5.sh b/docker/provision/gradle/gradle.sh similarity index 64% rename from docker/provision/gradle/gradle-8.5.sh rename to docker/provision/gradle/gradle.sh index 2f13a21099..60ce27348c 100755 --- a/docker/provision/gradle/gradle-8.5.sh +++ b/docker/provision/gradle/gradle.sh @@ -1,4 +1,4 @@ apt-get update && apt-get install -y wget unzip -wget https://github.com/gradle/gradle-distributions/releases/download/v8.5.0/gradle-8.5-bin.zip -O gradle.zip +wget https://github.com/gradle/gradle-distributions/releases/download/v$1/gradle-$1-bin.zip -O gradle.zip mkdir /opt/gradle && unzip -d /opt/gradle gradle.zip -mv /opt/gradle/gradle-8.5 /opt/gradle/gradle +mv /opt/gradle/gradle-$1 /opt/gradle/gradle diff --git a/docker/provision/jdk/java-22.sh b/docker/provision/jdk/java-22.sh deleted file mode 100755 index c442a6dc1a..0000000000 --- a/docker/provision/jdk/java-22.sh +++ /dev/null @@ -1,4 +0,0 @@ -apt-get update && apt-get install -y wget -wget https://download.java.net/java/early_access/jdk22/31/GPL/openjdk-22-ea+31_linux-x64_bin.tar.gz -O jdk.tar.gz -tar -xzf jdk.tar.gz -C /opt/ -mv /opt/jdk-22 /opt/jdk diff --git a/docker/provision/jdk/java-25.sh b/docker/provision/jdk/java-25.sh new file mode 100755 index 0000000000..a7d923d299 --- /dev/null +++ b/docker/provision/jdk/java-25.sh @@ -0,0 +1,4 @@ +apt-get update && apt-get install -y wget +wget https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25%2B36/OpenJDK25U-jdk_x64_linux_hotspot_25_36.tar.gz -O jdk.tar.gz +tar -xzf jdk.tar.gz -C /opt/ +mv /opt/jdk-25+36 /opt/jdk \ No newline at end of file diff --git a/docker/readme.md b/docker/readme.md index deb482f555..7bd05c64fc 100644 --- a/docker/readme.md +++ b/docker/readme.md @@ -13,7 +13,7 @@ By default, this is the latest released version. You can download a specific ver ### `ARG jdk=21` The jdk version to be used. Supported values: -- `22`(experimental, based on openjdk early access) +- `22`(based on openjdk GA) - `21` (default)(based on openjdk instead of adoptium) - `17` - `11` diff --git a/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java b/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java index a8e92b4b9c..222e9c0baf 100644 --- a/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java +++ b/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java @@ -7,7 +7,6 @@ import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; class NotifierHider { - public static class AstModificationNotifier implements AstModifyingAnnotationProcessor { private static Field lombokInvoked; diff --git a/src/core/lombok/AccessLevel.java b/src/core/lombok/AccessLevel.java index 9c138e53b5..8672222932 100644 --- a/src/core/lombok/AccessLevel.java +++ b/src/core/lombok/AccessLevel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 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 @@ -25,7 +25,24 @@ * Represents an AccessLevel. Used e.g. to specify the access level for generated methods and fields. */ public enum AccessLevel { - PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, + /** Represents the {@code public} access level. */ + PUBLIC, + + /** + * Acts exactly like {@code PACKAGE} - the package private access level. + * @deprecated This value was created at a time when a module-level access keyword was planned as a way of being prepared for the future. But that's not the direction java went in; a 'module access level' is not likely to ever exist. This enum acts like {@code PACKAGE} in every way. + */ + @Deprecated MODULE, + + /** Represents the {@code protected} access level (any code in the same package as well as any subtype). */ + PROTECTED, + + /** Represents the default access level: package private. (any code in the same package). */ + PACKAGE, + + /** Represents the {@code private} access level. */ + PRIVATE, + /** Represents not generating anything or the complete lack of a method. */ NONE; } diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index 06ca3853ba..efa60710e9 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 @@ *

* 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 27fe78e058..997c550bed 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-2026 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 @@ -29,6 +29,7 @@ import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; import lombok.core.configuration.IdentifierName; +import lombok.core.configuration.JacksonVersion; import lombok.core.configuration.LogDeclaration; import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; @@ -87,7 +88,7 @@ private ConfigurationKeys() {} * * If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated. */ - public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: false).") {}; + public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {}; /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. @@ -106,7 +107,7 @@ private ConfigurationKeys() {} public static final ConfigurationKey ADD_SUPPRESSWARNINGS_ANNOTATIONS = new ConfigurationKey("lombok.addSuppressWarnings", "Generate @java.lang.SuppressWarnings(\"all\") on all generated code (default: true).") {}; /** - * lombok configuration: {@code lombok.addNullAnnotations = }one of: [{@code none}, {@code javax}, {@code eclipse}, {@code jetbrains}, {@code netbeans}, {@code androidx}, {@code android.support}, {@code checkerframework}, {@code findbugs}, {@code spring}, {@code JML}, or a custom set of fully qualified annotation types]. + * lombok configuration: {@code lombok.addNullAnnotations = }one of: [{@code none}, {@code javax}, {@code eclipse}, {@code jetbrains}, {@code netbeans}, {@code androidx}, {@code android.support}, {@code checkerframework}, {@code findbugs}, {@code spring}, {@code JML}, {@code jspecify} or a custom set of fully qualified annotation types]. * * Lombok generally copies relevant nullity annotations from your source code to the right places. However, sometimes lombok generates code where the nullability of some node is not dependent on something in your source code. You can configure lombok to add an appropriate nullity annotation in this case.

    *
  • {@code none} (the default) - no annotations are added.
  • @@ -121,6 +122,7 @@ private ConfigurationKeys() {} *
  • {@code findbugs} - The annotations {@code edu.umd.cs.findbugs.annotations.NonNull} and {@code edu.umd.cs.findbugs.annotations.Nullable} are used.
  • *
  • {@code spring} - The annotations {@code org.springframework.lang.NonNull} and {@code org.springframework.lang.Nullable} are used.
  • *
  • {@code jml} - The annotations {@code org.jmlspecs.annotation.NonNull} and {@code org.jmlspecs.annotation.Nullable} are used.
  • + *
  • {@code jspecify} - The annotations {@code org.jspecify.annotations.NonNull} and {@code org.jspecify.annotations.Nullable} are used.
  • *
  • CUSTOM:fully.qualified.nonnull.annotation:fully.qualified.nullable.annotation to configure your own types; the nullable annotation (and the colon) are optional.
  • *
*

@@ -605,14 +607,14 @@ private 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.") {}; @@ -700,7 +702,7 @@ private ConfigurationKeys() {} * If set, any usage of {@code @WithBy} results in a warning / error. */ public static final ConfigurationKey WITHBY_FLAG_USAGE = new ConfigurationKey("lombok.withBy.flagUsage", "Emit a warning or error if @WithBy is used.") {}; - + // ----- Jacksonized ----- /** @@ -710,6 +712,16 @@ private ConfigurationKeys() {} */ public static final ConfigurationKey JACKSONIZED_FLAG_USAGE = new ConfigurationKey("lombok.jacksonized.flagUsage", "Emit a warning or error if @Jacksonized is used.") {}; + /** + * lombok configuration: {@code lombok.jacksonized.jacksonVersion} += {@code 2} / {@code 3}. + * + * Which version of the jackson annotations to generate. + * + * If no values specified: Generate version 2 and emit a warning to explicitly choose a jackson version in your {@code lombok.config}. + */ + public static final ConfigurationKey> JACKSONIZED_JACKSON_VERSION = new ConfigurationKey>("lombok.jacksonized.jacksonVersion", "The jackson major version(s) to generate (default: 2 + warning)") {}; + + // ----- Configuration System ----- /** @@ -720,7 +732,7 @@ private 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>. * @@ -736,11 +748,19 @@ private 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/CustomLog.java b/src/core/lombok/CustomLog.java index c6ea400d67..352ab92d6d 100644 --- a/src/core/lombok/CustomLog.java +++ b/src/core/lombok/CustomLog.java @@ -65,6 +65,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface CustomLog { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** * * Sets a custom topic/category. Note that this requires you to specify a parameter configuration for your custom logger that includes {@code TOPIC}. diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index ffa968c176..081ade3be8 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -28,7 +28,8 @@ /** * Generates getters for all fields, a useful toString method, and hashCode and equals implementations that check - * all non-transient fields. Will also generate setters for all non-final fields, as well as a constructor. + * all non-transient fields. Will also generate setters for all non-final fields, as well as a constructor + * (except that no constructor will be generated if any explicitly written constructors already exist). *

* Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}. *

diff --git a/src/core/lombok/Delegate.java b/src/core/lombok/Delegate.java deleted file mode 100644 index 0c5a4c2c66..0000000000 --- a/src/core/lombok/Delegate.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2010-2017 The Project Lombok Authors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package lombok; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated Use {@link lombok.experimental.Delegate} instead. - */ -@Target({ElementType.FIELD, ElementType.METHOD}) -@Retention(RetentionPolicy.SOURCE) -@Deprecated -public @interface Delegate { - /** - * Normally the type of the field is used as delegate type. However, to choose a different type to delegate, you can list one (or more) types here. Note that types with - * type arguments can only be done as a field type. A solution for this is to create a private inner interface/class with the appropriate types extended, and possibly - * with all methods you'd like to delegate listed, and then supply that class here. The field does not actually have to implement the type you're delegating; the - * type listed here is used only to determine which delegate methods to generate. - * - * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. - * - * @return For each method (not already in {@code java.lang.Object}) in these types, generate a delegate method. - */ - Class[] types() default {}; - - /** - * Each method in any of the types listed here (include supertypes) will not be delegated. - * - * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. - * - * @return For each method (not already in {@code java.lang.Object}) in these types, skip generating a delegate method (overrides {@code types()}). - */ - Class[] excludes() default {}; -} diff --git a/src/core/lombok/Generated.java b/src/core/lombok/Generated.java index 216ab14348..09608e4bb3 100644 --- a/src/core/lombok/Generated.java +++ b/src/core/lombok/Generated.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2016 The Project Lombok Authors. + * Copyright (C) 2015-2024 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,14 +27,14 @@ import java.lang.annotation.Target; /** - * Lombok will eventually automatically add this annotation to all generated constructors, methods, fields, and types. + * Lombok automatically adds this annotation to all generated constructors, methods, fields, and types. * * You can mark the presence of this annotation as 'ignore it' for all code style and bug finding tools. *

- * NB: As of v1.16.2 which introduces this annotation, lombok doesn't actually add this annotation; we're setting - * it up so that lombok jars in widespread use start having this, which will make it easier to actually apply it - * later on. By adding {@code lombok.addLombokGeneratedAnnotation = true} to {@code lombok.config} you can already - * get this behavior. + * NB: As of v1.18.34, lombok adds this annotation by default; before then you had to add a lombok config key. + *

+ * If you want to opt out (not recommended), you can add {@code lombok.addLombokGeneratedAnnotation = false} to + * {@code lombok.config}. */ @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.CLASS) diff --git a/src/core/lombok/SneakyThrows.java b/src/core/lombok/SneakyThrows.java index 2a8009a3b9..41b8f5169e 100644 --- a/src/core/lombok/SneakyThrows.java +++ b/src/core/lombok/SneakyThrows.java @@ -27,10 +27,10 @@ import java.lang.annotation.Target; /** - * @SneakyThrow will avoid javac's insistence that you either catch or throw onward any checked exceptions that + * @SneakyThrows will avoid javac's insistence that you either catch or throw onward any checked exceptions that * statements in your method body declare they generate. *

- * @SneakyThrow does not silently swallow, wrap into RuntimeException, or otherwise modify any exceptions of the listed + * @SneakyThrows does not silently swallow, wrap into RuntimeException, or otherwise modify any exceptions of the listed * checked exception types. The JVM does not check for the consistency of the checked exception system; javac does, * and this annotation lets you opt out of its mechanism. *

diff --git a/src/core/lombok/SuperBuilder.java b/src/core/lombok/SuperBuilder.java new file mode 100644 index 0000000000..62a9405682 --- /dev/null +++ b/src/core/lombok/SuperBuilder.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018-2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The SuperBuilder annotation creates a so-called 'builder' aspect to the class that is annotated with {@code @SuperBuilder}, but which works well when extending. + * It is similar to {@code @Builder}, except it is only legal on types, is less configurable, but allows you to {@code extends} other builder-able classes. + *

+ * All classes in the hierarchy must be annotated with {@code @SuperBuilder}. + *

+ * Lombok generates 2 inner 'builder' classes, which extend the parent class' builder class (unless your class doesn't have an extends clause). + * Lombok also generates a static method named {@code builder()}, and a protected constructor that takes 1 argument of the builderclass type. + *

+ * 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 {@code build()} method which returns a completed instance of the original type. + *

+ * Complete documentation is found at the project lombok features page for @SuperBuilder. + * + * @see Singular + */ +@Target(TYPE) +@Retention(SOURCE) +public @interface SuperBuilder { + /** @return Name of the method that creates a new builder instance. Default: {@code builder}. If the empty string, suppress generating the {@code builder} method. */ + String builderMethodName() default "builder"; + + /** @return Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + String buildMethodName() default "build"; + + /** + * 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. + */ + boolean toBuilder() default false; + + /** + * Prefix to prepend to 'set' methods in the generated builder class. By default, generated methods do not include a prefix. + * + * For example, a method normally generated as {@code someField(String someField)} would instead be + * generated as {@code withSomeField(String someField)} if using {@code @SuperBuilder(setterPrefix = "with")}. + * + * Note that using "with" to prefix builder setter methods is strongly discouraged as "with" normally + * suggests immutable data structures, and builders by definition are mutable objects. + * + * For {@code @Singular} fields, the generated methods are called {@code withName}, {@code withNames}, and {@code clearNames}, instead of + * the default {@code name}, {@code names}, and {@code clearNames}. + * + * This prefix only applies to the 'set' methods for the fields of the annotated class. + * For consistency reasons, you should use the same prefix on all superclasses and subclasses that use {@code @SuperBuilder}. + * + * @return The prefix to prepend to generated method names. + */ + String setterPrefix() default ""; +} diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index 390e9b71b3..19026ca51a 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 The Project Lombok Authors. + * Copyright (C) 2009-2024 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 @@ -182,6 +182,7 @@ public List getAsStringList(String methodName) { "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); } out.add((String) result); + idx++; } return Collections.unmodifiableList(out); diff --git a/src/core/lombok/core/JacksonAnnotationType.java b/src/core/lombok/core/JacksonAnnotationType.java new file mode 100644 index 0000000000..306f28cc90 --- /dev/null +++ b/src/core/lombok/core/JacksonAnnotationType.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +public enum JacksonAnnotationType { + JSON_POJO_BUILDER2("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder"), + JSON_POJO_BUILDER3("tools.jackson.databind.annotation.JsonPOJOBuilder"), + JSON_DESERIALIZE2("com.fasterxml.jackson.databind.annotation.JsonDeserialize"), + JSON_DESERIALIZE3("tools.jackson.databind.annotation.JsonDeserialize"), + JSON_PROPERTY2("com.fasterxml.jackson.annotation.JsonProperty"), + JSON_IGNORE2("com.fasterxml.jackson.annotation.JsonIgnore"), + ; + + private final String qualifiedName; + private final String[] qualifiedNameStringArr; + private final char[][] qualifiedNameCharArrArr; + + private JacksonAnnotationType(final String qualifiedName) { + this.qualifiedName = qualifiedName; + String[] parts = qualifiedName.split("\\."); + this.qualifiedNameStringArr = parts; + char[][] caa = new char[parts.length][]; + for (int i = 0; i < parts.length; i++) { + caa[i] = parts[i].toCharArray(); + } + this.qualifiedNameCharArrArr = caa; + } + + public String getQualifiedName() { + return qualifiedName; + } + + // Do not modify the returned array. + public String[] getQualifiedNameAsStringArray() { + return qualifiedNameStringArr; + } + + // Do not modify the returned array. + public char[][] getQualifiednameAsCharArrayArray() { + return qualifiedNameCharArrArr; + } +} diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java index 60dea0f299..f31e736317 100644 --- a/src/core/lombok/core/LombokInternalAliasing.java +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -47,6 +47,7 @@ public static String processAliases(String in) { m1.put("lombok.experimental.var", "lombok.var"); m1.put("lombok.Delegate", "lombok.experimental.Delegate"); m1.put("lombok.experimental.Wither", "lombok.With"); + m1.put("lombok.experimental.SuperBuilder", "lombok.SuperBuilder"); ALIASES = Collections.unmodifiableMap(m1); Map> m2 = new HashMap>(); diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index c84718791e..52bae60757 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2023 The Project Lombok Authors. + * Copyright (C) 2009-2026 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 @@ -30,7 +30,7 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "1.18.31"; + private static final String VERSION = "1.18.47"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; // private static final String RELEASE_NAME = "Envious Ferret"; diff --git a/src/core/lombok/core/configuration/ConfigurationDataType.java b/src/core/lombok/core/configuration/ConfigurationDataType.java index 50b47a2eb1..4c05f28d26 100644 --- a/src/core/lombok/core/configuration/ConfigurationDataType.java +++ b/src/core/lombok/core/configuration/ConfigurationDataType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2019 The Project Lombok Authors. + * Copyright (C) 2013-2026 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 @@ -102,23 +102,30 @@ public final class ConfigurationDataType { SIMPLE_TYPES = map; } - private static ConfigurationValueParser enumParser(Type enumType) { + private static ConfigurationValueParser enumParser(final Type enumType) { final Class type = (Class) enumType; @SuppressWarnings("rawtypes") final Class rawType = type; return new ConfigurationValueParser() { @SuppressWarnings("unchecked") @Override public Object parse(String value) { - try { - return Enum.valueOf(rawType, value); - } catch (Exception e) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (Character.isUpperCase(c) && i > 0) sb.append("_"); - sb.append(Character.toUpperCase(c)); + if (enumType instanceof Class && MappedConfigEnum.class.isAssignableFrom(type)) { + for (Object enumVal : ((Class) enumType).getEnumConstants()) { + if (((MappedConfigEnum) enumVal).matches(value)) return enumVal; + } + throw new IllegalArgumentException("Invalid value: " + value); + } else { + try { + return Enum.valueOf(rawType, value); + } catch (Exception e) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (Character.isUpperCase(c) && i > 0) sb.append("_"); + sb.append(Character.toUpperCase(c)); + } + return Enum.valueOf(rawType, sb.toString()); } - return Enum.valueOf(rawType, sb.toString()); } } @@ -129,7 +136,7 @@ private static ConfigurationValueParser enumParser(Type enumType) { @Override public String exampleValue() { ExampleValueString evs = type.getAnnotation(ExampleValueString.class); if (evs != null) return evs.value(); - return Arrays.toString(type.getEnumConstants()).replace(",", " |"); + return Arrays.toString(type.getEnumConstants()).replace(",", " | "); } }; } diff --git a/src/core/lombok/core/configuration/ConfigurationFile.java b/src/core/lombok/core/configuration/ConfigurationFile.java index 8e4e687c58..a510368ca5 100644 --- a/src/core/lombok/core/configuration/ConfigurationFile.java +++ b/src/core/lombok/core/configuration/ConfigurationFile.java @@ -97,7 +97,7 @@ private static boolean fileExists(File file) { return file.exists() && file.isFile(); } - private static String read(InputStream is) throws IOException { + static String read(InputStream is) throws IOException { byte[] b = buffers.get(); ByteArrayOutputStream out = new ByteArrayOutputStream(); while (true) { @@ -111,7 +111,7 @@ private static String read(InputStream is) throws IOException { private static class RegularConfigurationFile extends ConfigurationFile { private final File file; private ConfigurationFile parent; - + private RegularConfigurationFile(File file) { super(file.getPath()); this.file = file; @@ -172,7 +172,7 @@ CharSequence contents() throws IOException { is.close(); } } - + @Override ConfigurationFile parent() { if (parent == null) { File parentFile = file.getParentFile().getParentFile(); @@ -322,15 +322,15 @@ private CharSequenceConfigurationFile(String identifier, CharSequence contents, @Override CharSequence contents() throws IOException { return contents; } - + @Override boolean exists() { return true; } - + @Override public ConfigurationFile resolve(String path) { return null; } - + @Override ConfigurationFile parent() { return null; } diff --git a/src/core/lombok/core/configuration/ConfigurationValueType.java b/src/core/lombok/core/configuration/ConfigurationValueType.java index a44a5a834a..c1d2e4e6d3 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 @@ * If a type used in {@link ConfigurationKey} type argument implements this interface, * it is expected to provide the following three static methods: *

    - *
  • public static SELF valueOf(String value) - *
  • public static String description() - *
  • public static String exampleValue() + *
  • public static SELF valueOf(String value)
  • + *
  • public static String description()
  • + *
  • public static String exampleValue()
  • *
* None of them should throw checked exceptions. * Based on these methods, an instance of {@link ConfigurationValueParser} is created diff --git a/src/core/lombok/core/configuration/JacksonVersion.java b/src/core/lombok/core/configuration/JacksonVersion.java new file mode 100644 index 0000000000..480dd973f4 --- /dev/null +++ b/src/core/lombok/core/configuration/JacksonVersion.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +public enum JacksonVersion implements MappedConfigEnum { + TWO, + THREE, + ; + + public static final String AMBIGUOUS_JACKSON_VERSION_WARNING_TEXT = + "Ambiguous: Jackson2 and Jackson3 exist; define which variant(s) you want in 'lombok.config'. See https://projectlombok.org/features/experimental/Jacksonized"; + + @Override public boolean matches(String value) { + if (this == TWO) return "2".equals(value); + return "3".equals(value); + } + + @Override public String toString() { + if (this == TWO) return "2"; + return "3"; + } +} diff --git a/src/core/lombok/core/configuration/MappedConfigEnum.java b/src/core/lombok/core/configuration/MappedConfigEnum.java new file mode 100644 index 0000000000..6f485b456c --- /dev/null +++ b/src/core/lombok/core/configuration/MappedConfigEnum.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2026 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +public interface MappedConfigEnum { + boolean matches(String value); +} diff --git a/src/core/lombok/core/configuration/NullAnnotationLibrary.java b/src/core/lombok/core/configuration/NullAnnotationLibrary.java index e1d31004c0..52858e9568 100644 --- a/src/core/lombok/core/configuration/NullAnnotationLibrary.java +++ b/src/core/lombok/core/configuration/NullAnnotationLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 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 @@ -73,7 +73,8 @@ public boolean isTypeUse() { public static final NullAnnotationLibrary FINDBUGS = new NullAnnotationLibrary("findbugs", "edu.umd.cs.findbugs.annotations.NonNull", "edu.umd.cs.findbugs.annotations.Nullable", false); public static final NullAnnotationLibrary SPRING = new NullAnnotationLibrary("spring", "org.springframework.lang.NonNull", "org.springframework.lang.Nullable", false); public static final NullAnnotationLibrary JML = new NullAnnotationLibrary("jml", "org.jmlspecs.annotation.NonNull", "org.jmlspecs.annotation.Nullable", false); - + public static final NullAnnotationLibrary JSPECIFY = new NullAnnotationLibrary("jspecify", "org.jspecify.annotations.NonNull", "org.jspecify.annotations.Nullable", true); + private static final List ALL_AVAILABLE; private static final String EXAMPLE_VALUE; @@ -155,4 +156,8 @@ private static String verifyTypeName(String fqn) { if (atStart) throw new IllegalArgumentException(MSG + fqn); return fqn; } + + @Override public String toString() { + return key; + } } diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 06bac16fdd..4f546352d9 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2022 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 @@ -79,7 +79,7 @@ public static int primeForNull() { 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,40 @@ public static int primeForNull() { "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", + "tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper", + "tools.jackson.dataformat.xml.annotation.JacksonXmlProperty", + "tools.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", @@ -440,10 +473,16 @@ public static int primeForNull() { "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper", "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty", "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText", + "tools.jackson.databind.annotation.JsonDeserialize", + "tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper", + "tools.jackson.dataformat.xml.annotation.JacksonXmlProperty", + "tools.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", @@ -456,6 +495,7 @@ public static int primeForNull() { "com.fasterxml.jackson.annotation.JsonTypeName", "com.fasterxml.jackson.annotation.JsonView", "com.fasterxml.jackson.databind.annotation.JsonNaming", + "tools.jackson.databind.annotation.JsonNaming", })); } @@ -906,13 +946,17 @@ public enum JavadocTag { private Pattern pattern; JavadocTag(String regexpFragment) { - pattern = Pattern.compile("\\s?^[ \\t]*\\**[ \\t]*" + regexpFragment + "(\\S|\\s)*?(?=(\\s^\\s*\\**\\s*@|\\Z))", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + pattern = Pattern.compile("\\s?^[ \\t]*\\**[ \\t]*" + regexpFragment + ".*?(?=(\\s^\\s*\\**\\s*@|\\Z))", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL); } } - public static String stripLinesWithTagFromJavadoc(String javadoc, JavadocTag tag) { + public static String stripLinesWithTagFromJavadoc(String javadoc, JavadocTag... tags) { if (javadoc == null || javadoc.isEmpty()) return javadoc; - return tag.pattern.matcher(javadoc).replaceAll("").trim(); + String result = javadoc; + for (JavadocTag tag : tags) { + result = tag.pattern.matcher(result).replaceAll("").trim(); + } + return result; } public static String stripSectionsFromJavadoc(String javadoc) { @@ -968,17 +1012,36 @@ public static String addReturnsUpdatedSelfIfNeeded(String in) { public static String addJavadocLine(String in, String line) { if (in == null) return line; - if (in.endsWith("\n")) return in + line + "\n"; + if (in.endsWith("\n")) return in + line; return in + "\n" + line; } public static String getParamJavadoc(String methodComment, String param) { if (methodComment == null || methodComment.isEmpty()) return methodComment; - Pattern pattern = Pattern.compile("@param " + param + " (\\S|\\s)+?(?=^ ?@)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Pattern pattern = Pattern.compile("@param " + param + " .+?(?=^ ?@|\\z)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL); Matcher matcher = pattern.matcher(methodComment); if (matcher.find()) { return matcher.group(); } return null; } + + public static String getConstructorJavadocHeader(String typeName) { + return "Creates a new {@code " + typeName + "} instance.\n\n"; + } + + public static String getConstructorParameterJavadoc(String paramName, String fieldJavadoc) { + String fieldBaseJavadoc = stripSectionsFromJavadoc(fieldJavadoc); + + String paramJavadoc = getParamJavadoc(fieldBaseJavadoc, paramName); + if (paramJavadoc != null) { + return paramJavadoc; + } + + String javadocWithoutTags = stripLinesWithTagFromJavadoc(fieldBaseJavadoc, JavadocTag.PARAM, JavadocTag.RETURN); + if (javadocWithoutTags != null) { + return "@param " + paramName + " " + javadocWithoutTags; + } + return null; + } } diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 4a0854a4ac..dfe754a2b7 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 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 @@ -40,6 +40,7 @@ import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -354,7 +355,7 @@ public static boolean isComplete(CompilationUnitDeclaration unit) { case TYPE: return buildType((TypeDeclaration) node); case FIELD: - return buildField((FieldDeclaration) node, null); + return buildField((FieldDeclaration) node); case INITIALIZER: return buildInitializer((Initializer) node); case METHOD: @@ -393,17 +394,39 @@ private List buildTypes(TypeDeclaration[] children) { private EclipseNode buildType(TypeDeclaration type) { if (setAndGetAsHandled(type)) return null; List childNodes = new ArrayList(); - childNodes.addAll(buildFields(type.fields, getRecordFieldAnnotations(type))); + childNodes.addAll(buildRecordComponents(getRecordComponents(type))); + childNodes.addAll(buildFields(type.fields)); childNodes.addAll(buildTypes(type.memberTypes)); childNodes.addAll(buildMethods(type.methods)); childNodes.addAll(buildAnnotations(type.annotations, false)); return putInMap(new EclipseNode(this, type, childNodes, Kind.TYPE)); } - private Collection buildFields(FieldDeclaration[] children, Annotation[][] annotations) { + private Collection buildRecordComponents(AbstractVariableDeclaration[] children) { + if (children == null) return Collections.emptyList(); + List childNodes = new ArrayList(); + for (int i = 0; i < children.length; i++) { + addIfNotNull(childNodes, buildRecordComponent(children[i])); + } + return childNodes; + } + + private EclipseNode buildRecordComponent(AbstractVariableDeclaration field) { + List childNodes = new ArrayList(); + addIfNotNull(childNodes, buildTypeUse(field.type)); + addIfNotNull(childNodes, buildStatement(field.initialization)); + childNodes.addAll(buildAnnotations(field.annotations, true)); + FieldDeclaration fieldDeclaration = new FieldDeclaration(field.name, field.sourceStart, field.sourceEnd); + fieldDeclaration.type = field.type; + fieldDeclaration.modifiers = field.modifiers | Eclipse.AccRecord; + fieldDeclaration.annotations = field.annotations; + return putInMap(new EclipseNode(this, fieldDeclaration, childNodes, Kind.FIELD)); + } + + private Collection buildFields(FieldDeclaration[] children) { List childNodes = new ArrayList(); if (children != null) for (int i = 0; i < children.length; i++) { - addIfNotNull(childNodes, buildField(children[i], annotations[i])); + addIfNotNull(childNodes, buildField(children[i])); } return childNodes; } @@ -414,13 +437,14 @@ private static List singleton(T item) { return list; } - private EclipseNode buildField(FieldDeclaration field, Annotation[] annotations) { + private EclipseNode buildField(FieldDeclaration field) { if (field instanceof Initializer) return buildInitializer((Initializer) field); if (setAndGetAsHandled(field)) return null; + if (isRecordField(field)) return null; List childNodes = new ArrayList(); addIfNotNull(childNodes, buildTypeUse(field.type)); addIfNotNull(childNodes, buildStatement(field.initialization)); - childNodes.addAll(buildAnnotations(annotations != null ? annotations : field.annotations, true)); + childNodes.addAll(buildAnnotations(field.annotations, true)); return putInMap(new EclipseNode(this, field, childNodes, Kind.FIELD)); } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index df9e5451cc..bedf46dd44 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 static EclipseAST getAST(CompilationUnitDeclaration ast, boolean forceReb /** * 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 1e4c4f23ba..7eaaf7b6c3 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.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 @@ -22,8 +22,8 @@ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; -import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.EcjAugments.*; +import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.EclipseReflectiveMembers.*; import java.lang.reflect.Array; @@ -38,6 +38,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -60,7 +62,6 @@ import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; -import org.eclipse.jdt.internal.compiler.ast.ExtendedStringLiteral; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; @@ -86,7 +87,6 @@ import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; -import org.eclipse.jdt.internal.compiler.ast.StringLiteralConcatenation; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; @@ -123,6 +123,7 @@ import lombok.core.debug.ProblemReporter; import lombok.core.handlers.HandlerUtil; import lombok.core.handlers.HandlerUtil.FieldAccess; +import lombok.core.handlers.HandlerUtil.JavadocTag; import lombok.eclipse.EcjAugments; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAST; @@ -416,20 +417,6 @@ private static Expression copyAnnotationMemberValue0(Expression in) { if (in instanceof LongLiteral) return LongLiteral.buildLongLiteral(((Literal) in).source(), s, e); if (in instanceof StringLiteral) return new StringLiteral(((Literal) in).source(), s, e, reflectInt(STRING_LITERAL__LINE_NUMBER, in) + 1); - if (in instanceof ExtendedStringLiteral) { - StringLiteral str = new StringLiteral(((Literal) in).source(), s, e, reflectInt(STRING_LITERAL__LINE_NUMBER, in) + 1); - StringLiteral empty = new StringLiteral(new char[0], s, e, reflectInt(STRING_LITERAL__LINE_NUMBER, in) + 1); - return new ExtendedStringLiteral(str, empty); - } - if (in instanceof StringLiteralConcatenation) { - Expression[] literals = ((StringLiteralConcatenation) in).literals; - // 0 and 1 len shouldn't happen. - if (literals.length == 0) return new StringLiteral(new char[0], s, e, 0); - if (literals.length == 1) return copyAnnotationMemberValue0(literals[0]); - StringLiteralConcatenation c = new StringLiteralConcatenation((StringLiteral) literals[0], (StringLiteral) literals[1]); - for (int i = 2; i < literals.length; i++) c = c.extendsWith((StringLiteral) literals[i]); - return c; - } // enums and field accesses (as long as those are references to compile time constant literals that's also acceptable) @@ -833,17 +820,36 @@ public static Annotation[] findCopyableAnnotations(EclipseNode node) { } /** - * 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); } /** @@ -1387,6 +1393,7 @@ public static TypeReference makeType(TypeBinding binding, ASTNode pos, boolean a /** * Turns an {@code AccessLevel} instance into the flag bit used by eclipse. */ + @SuppressWarnings("deprecation") // We have to use MODULE here to make it act according to spec, which is to treat it like `PACKAGE`. public static int toEclipseModifier(AccessLevel value) { switch (value) { case MODULE: @@ -2113,7 +2120,7 @@ public static Annotation[] addGenerated(EclipseNode node, ASTNode source, Annota if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_JAKARTA_GENERATED_ANNOTATIONS))) { result = addAnnotation(source, result, JAKARTA_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0)); } - if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) { + if (!Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) { result = addAnnotation(source, result, LOMBOK_GENERATED); } return result; @@ -2156,7 +2163,7 @@ static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotatio for (int i = 0; i < args.length; i++) { args[i].sourceStart = pS; args[i].sourceEnd = pE; - na.memberValuePairs[i] = (MemberValuePair) args[i]; + na.memberValuePairs[i] = (MemberValuePair) args[i]; } setGeneratedBy(na.memberValuePairs[0], source); setGeneratedBy(na.memberValuePairs[0].value, source); @@ -2749,10 +2756,8 @@ public static boolean isClassEnumOrRecord(EclipseNode typeNode) { * Returns {@code true} if the provided node is a record declaration (so, not an annotation definition, interface, enum, or plain class). */ public static boolean isRecord(EclipseNode typeNode) { - TypeDeclaration typeDecl = null; - if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); - int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; - return (modifiers & AccRecord) != 0; + ASTNode node = typeNode.get(); + return node instanceof TypeDeclaration && isRecord((TypeDeclaration) node); } /** @@ -2786,6 +2791,14 @@ public static boolean isStaticAllowed(EclipseNode typeNode) { return typeNode.isStatic() || typeNode.up() == null || typeNode.up().getKind() == Kind.COMPILATION_UNIT || isRecord(typeNode); } + /** + * Returns {@code true} if the provided type declaration is a record declaration (so, not an annotation definition, interface, enum, or plain class). + */ + public static boolean isRecord(TypeDeclaration typeDecl) { + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + return (modifiers & AccRecord) != 0; + } + public static AbstractVariableDeclaration[] getRecordComponents(TypeDeclaration typeDeclaration) { if (typeDeclaration == null || (typeDeclaration.modifiers & AccRecord) == 0) return null; try { @@ -2812,32 +2825,51 @@ public static Annotation[][] getRecordFieldAnnotations(TypeDeclaration typeDecla return annotations; } + private static final Pattern JAVADOC_PATTERN = Pattern.compile("^\\s*\\/\\*\\*(.*?)\\*\\/", Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern LEADING_ASTERISKS_PATTERN = Pattern.compile("^\\s*\\* ?", Pattern.MULTILINE); + public static String getDocComment(EclipseNode eclipseNode) { if (eclipseNode.getAst().getSource() == null) return null; + int start = -1; + int end = -1; + final ASTNode node = eclipseNode.get(); if (node instanceof FieldDeclaration) { FieldDeclaration fieldDeclaration = (FieldDeclaration) node; - char[] rawContent = CharOperation.subarray(eclipseNode.getAst().getSource(), fieldDeclaration.declarationSourceStart, fieldDeclaration.declarationSourceEnd); + start = fieldDeclaration.declarationSourceStart; + end = fieldDeclaration.declarationSourceEnd; + } else if (node instanceof AbstractMethodDeclaration) { + AbstractMethodDeclaration abstractMethodDeclaration = (AbstractMethodDeclaration) node; + start = abstractMethodDeclaration.declarationSourceStart; + end = abstractMethodDeclaration.declarationSourceEnd; + } + if (start != -1 && end != -1) { + char[] rawContent = CharOperation.subarray(eclipseNode.getAst().getSource(), start, end); String rawContentString = new String(rawContent); - int startIndex = rawContentString.indexOf("/**"); - int endIndex = rawContentString.indexOf("*/"); - if (startIndex != -1 && endIndex != -1) { + Matcher javadocMatcher = JAVADOC_PATTERN.matcher(rawContentString); + if (javadocMatcher.find()) { + String javadoc = javadocMatcher.group(1); /* Remove all leading asterisks */ - return rawContentString.substring(startIndex + 3, endIndex).replaceAll("(?m)^\\s*\\* ?", "").trim(); + return LEADING_ASTERISKS_PATTERN.matcher(javadoc).replaceAll("").trim(); } } return null; } + public static void setDocComment(EclipseNode typeNode, EclipseNode eclipseNode, String doc) { + setDocComment((CompilationUnitDeclaration) eclipseNode.top().get(), (TypeDeclaration) typeNode.get(), eclipseNode.get(), doc); + } + public static void setDocComment(CompilationUnitDeclaration cud, EclipseNode eclipseNode, String doc) { setDocComment(cud, (TypeDeclaration) upToTypeNode(eclipseNode).get(), eclipseNode.get(), doc); } - + public static void setDocComment(CompilationUnitDeclaration cud, TypeDeclaration type, ASTNode node, String doc) { if (doc == null) return; ICompilationUnit compilationUnit = cud.compilationResult.compilationUnit; + if (compilationUnit == null) return; if (compilationUnit.getClass().equals(COMPILATION_UNIT)) { try { compilationUnit = (ICompilationUnit) Permit.invoke(COMPILATION_UNIT_ORIGINAL_FROM_CLONE, compilationUnit); diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java index 54f1a55135..61e49326bd 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 @@ protected TypeReference cloneParamType(int index, List typeArgs, 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 0a6b3447be..c39acdc7e2 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/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 @@ -59,7 +59,6 @@ import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.Receiver; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; -import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; @@ -778,27 +777,23 @@ static Receiver generateBuildReceiver(BuilderJob job) { if (mandatories.size() == 0) return null; - int pS = job.source.sourceStart, pE = job.source.sourceEnd; - char[][] nameCalled = fromQualifiedName(CheckerFrameworkVersion.NAME__CALLED); - SingleMemberAnnotation ann = new SingleMemberAnnotation(new QualifiedTypeReference(nameCalled, poss(job.source, nameCalled.length)), pS); + Expression memberValue; if (mandatories.size() == 1) { - ann.memberValue = new StringLiteral(mandatories.get(0), 0, 0, 0); + memberValue = new StringLiteral(mandatories.get(0), 0, 0, 0); } else { ArrayInitializer arr = new ArrayInitializer(); - arr.sourceStart = pS; - arr.sourceEnd = pE; arr.expressions = new Expression[mandatories.size()]; for (int i = 0; i < arr.expressions.length; i++) { - arr.expressions[i] = new StringLiteral(mandatories.get(i), pS, pE, 0); + arr.expressions[i] = new StringLiteral(mandatories.get(i), job.source.sourceStart, job.source.sourceEnd, 0); } - ann.memberValue = arr; + memberValue = arr; } TypeReference typeReference = job.createBuilderTypeReference(); int len = typeReference.getTypeName().length; typeReference.annotations = new Annotation[len][]; - typeReference.annotations[len - 1] = new Annotation[] {ann}; + typeReference.annotations[len - 1] = EclipseHandlerUtil.addAnnotation(job.source, null, nameCalled, memberValue); return new Receiver(new char[] { 't', 'h', 'i', 's' }, 0, typeReference, null, 0); } @@ -1047,13 +1042,13 @@ private void makePrefixedSetterMethodForBuilder(BuilderJob job, BuilderFieldData } 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), - job.sourceNode, methodAnnsList, bfd.annotations != null ? Arrays.asList(copyAnnotations(source, bfd.annotations)) : Collections.emptyList()); + job.sourceNode, methodAnnsList, bfd.annotations != null ? Arrays.asList(copyAnnotations(source, bfd.annotations)) : Collections.emptyList(), false); if (job.sourceNode.up().getKind() == Kind.METHOD) { - copyJavadocFromParam(bfd.originalFieldNode.up(), setter, td, bfd.name.toString()); + copyJavadocFromParam(bfd.originalFieldNode.up(), setter, td, new String(bfd.name)); } else { copyJavadoc(bfd.originalFieldNode, setter, td, CopyJavadoc.SETTER, true); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java index 464a0efd51..a3584ef704 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java @@ -30,7 +30,7 @@ import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.SuperBuilder; +import lombok.SuperBuilder; import lombok.spi.Provides; @Provides @@ -41,7 +41,7 @@ public class HandleBuilderDefault extends EclipseAnnotationHandler onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@NoArgsConstructor(onConstructor=...)"); + } handleConstructor.generateConstructor(typeNode, level, Collections.emptyList(), force, staticName, SkipIfConstructorExists.NO, onConstructor, annotationNode); } @@ -125,6 +128,9 @@ public static class HandleRequiredArgsConstructor extends EclipseAnnotationHandl } List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@RequiredArgsConstructor(onConstructor=...)"); + } handleConstructor.generateConstructor( typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, @@ -132,6 +138,36 @@ typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstruc } } + @Provides + public static class HandleAllArgsConstructor extends EclipseAnnotationHandler { + private static final String NAME = AllArgsConstructor.class.getSimpleName(); + + private HandleConstructor handleConstructor = new HandleConstructor(); + + @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); + + EclipseNode typeNode = annotationNode.up(); + if (!checkLegality(typeNode, annotationNode, NAME)) return; + AllArgsConstructor ann = annotation.getInstance(); + AccessLevel level = ann.access(); + if (level == AccessLevel.NONE) return; + String staticName = ann.staticName(); + if (annotation.isExplicit("suppressConstructorProperties")) { + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); + } + + List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@AllArgsConstructor(onConstructor=...)"); + } + + handleConstructor.generateConstructor( + typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + onConstructor, annotationNode); + } + } + private static List findRequiredFields(EclipseNode typeNode) { return findFields(typeNode, true); } @@ -167,33 +203,6 @@ static List findAllFields(EclipseNode typeNode, boolean evenFinalIn return fields; } - @Provides - public static class HandleAllArgsConstructor extends EclipseAnnotationHandler { - private static final String NAME = AllArgsConstructor.class.getSimpleName(); - - private HandleConstructor handleConstructor = new HandleConstructor(); - - @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { - handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); - - EclipseNode typeNode = annotationNode.up(); - if (!checkLegality(typeNode, annotationNode, NAME)) return; - AllArgsConstructor ann = annotation.getInstance(); - AccessLevel level = ann.access(); - if (level == AccessLevel.NONE) return; - String staticName = ann.staticName(); - if (annotation.isExplicit("suppressConstructorProperties")) { - annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); - } - - List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); - - handleConstructor.generateConstructor( - typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, - onConstructor, annotationNode); - } - } - static boolean checkLegality(EclipseNode typeNode, EclipseNode errorNode, String name) { if (!isClassOrEnum(typeNode)) { errorNode.addError(name + " is only supported on a class or an enum."); @@ -277,7 +286,8 @@ public void generate( ConstructorDeclaration constr = createConstructor( staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fieldsToParam, forceDefaults, sourceNode, onConstructor); - injectMethod(typeNode, constr); + EclipseNode constructorNode = injectMethod(typeNode, constr); + generateConstructorJavadoc(typeNode, constructorNode, fieldsToParam); } generateStaticConstructor(staticConstrRequired, typeNode, staticName, level, fieldsToParam, source); } @@ -285,7 +295,8 @@ public void generate( private void generateStaticConstructor(boolean staticConstrRequired, EclipseNode typeNode, String staticName, AccessLevel level, Collection fields, ASTNode source) { if (staticConstrRequired) { MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, fields, source); - injectMethod(typeNode, staticConstr); + EclipseNode constructorNode = injectMethod(typeNode, staticConstr); + generateConstructorJavadoc(typeNode, constructorNode, fields); } } @@ -575,4 +586,29 @@ public MethodDeclaration createStaticConstructor(AccessLevel level, String name, constructor.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); return constructor; } + + private void generateConstructorJavadoc(EclipseNode typeNode, EclipseNode constructorNode, Collection fields) { + try { + if (fields.isEmpty()) return; + + String constructorJavadoc = getConstructorJavadocHeader(typeNode.getName()); + boolean fieldDescriptionAdded = false; + for (EclipseNode fieldNode : fields) { + String paramName = String.valueOf(removePrefixFromField(fieldNode)); + String fieldJavadoc = getDocComment(fieldNode); + String paramJavadoc = getConstructorParameterJavadoc(paramName, fieldJavadoc); + + if (paramJavadoc == null) { + paramJavadoc = "@param " + paramName; + } else { + fieldDescriptionAdded = true; + } + + constructorJavadoc = addJavadocLine(constructorJavadoc, paramJavadoc); + } + if (fieldDescriptionAdded) { + setDocComment(typeNode, constructorNode, constructorJavadoc); + } + } catch (Exception ignore) {} + } } diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 2d8b112f47..9bec523a3f 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 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 @@ 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)) { @@ -147,6 +147,9 @@ public void handle(AnnotationValues annotation, Annotation ast, EclipseN if (node == null) return; List onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode); + if (!onMethod.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Getter(onMethod=...)"); + } switch (node.getKind()) { case FIELD: @@ -276,9 +279,15 @@ public MethodDeclaration createGetter(TypeDeclaration parent, EclipseNode fieldN 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); @@ -323,7 +332,7 @@ public Statement[] createSimpleGetterBody(ASTNode source, EclipseNode fieldNode) TYPE_MAP = Collections.unmodifiableMap(m); } - private static char[] valueName = "value".toCharArray(); + private static char[] valueName = "$value".toCharArray(); private static char[] actualValueName = "actualValue".toCharArray(); private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK; diff --git a/src/core/lombok/eclipse/handlers/HandleJacksonized.java b/src/core/lombok/eclipse/handlers/HandleJacksonized.java index 466cc338f8..4bb05ad99c 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-2026 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 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; @@ -42,12 +43,14 @@ import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; +import lombok.core.JacksonAnnotationType; import lombok.core.AST.Kind; +import lombok.core.configuration.JacksonVersion; import lombok.core.handlers.HandlerUtil; -import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.SuperBuilder; +import lombok.experimental.Accessors; +import lombok.SuperBuilder; import lombok.extern.jackson.Jacksonized; import lombok.spi.Provides; @@ -59,26 +62,26 @@ @Provides @HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated). 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"); - + static boolean hasAnnotation(EclipseNode node, JacksonAnnotationType annotation) { + return EclipseHandlerUtil.hasAnnotation(annotation.getQualifiedName(), node); + } + @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,19 @@ public class HandleJacksonized extends EclipseAnnotationHandler { return; } + List jacksonVersions = readConfiguredJacksonVersions(annotationNode); + + boolean jacksonizedBuilder = builderAnnotationNode != null || superBuilderAnnotationNode != null; + if (jacksonizedBuilder) { + handleJacksonizedBuilder(ast, annotationNode, annotatedNode, tdNode, td, builderAnnotationNode, superBuilderAnnotationNode, jacksonVersions); + } + + 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, List jacksonVersions) { boolean isAbstract = (td.modifiers & ClassFileConstants.AccAbstract) != 0; if (isAbstract) { annotationNode.addError("Builders on abstract classes cannot be @Jacksonized (the builder would never be used)."); @@ -118,7 +134,7 @@ public class HandleJacksonized extends EclipseAnnotationHandler { } // Insert @JsonDeserialize on annotated class. - if (hasAnnotation("com.fasterxml.jackson.databind.annotation.JsonDeserialize", tdNode)) { + if (hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE2) || hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE3)) { annotationNode.addError("@JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson."); return; } @@ -126,7 +142,14 @@ public class HandleJacksonized extends EclipseAnnotationHandler { TypeReference builderClassExpression = namePlusTypeParamsToTypeReference(builderClassNode, null, p); ClassLiteralAccess builderClassLiteralAccess = new ClassLiteralAccess(td.sourceEnd, builderClassExpression); MemberValuePair builderMvp = new MemberValuePair("builder".toCharArray(), td.sourceStart, td.sourceEnd, builderClassLiteralAccess); - td.annotations = addAnnotation(td, td.annotations, JSON_DESERIALIZE_ANNOTATION, builderMvp); + + if (jacksonVersions.contains(JacksonVersion.TWO)) { + td.annotations = addAnnotation(td, td.annotations, JacksonAnnotationType.JSON_DESERIALIZE2.getQualifiednameAsCharArrayArray(), builderMvp); + } + + if (jacksonVersions.contains(JacksonVersion.THREE)) { + td.annotations = addAnnotation(td, td.annotations, JacksonAnnotationType.JSON_DESERIALIZE3.getQualifiednameAsCharArrayArray(), builderMvp); + } // Copy annotations from the class to the builder class. Annotation[] copyableAnnotations = findJacksonAnnotationsOnClass(td, tdNode); @@ -137,11 +160,63 @@ public class HandleJacksonized extends EclipseAnnotationHandler { MemberValuePair withPrefixMvp = new MemberValuePair("withPrefix".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, withPrefixLiteral); StringLiteral buildMethodNameLiteral = new StringLiteral(buildMethodName.toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, 0); MemberValuePair buildMethodNameMvp = new MemberValuePair("buildMethodName".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, buildMethodNameLiteral); - builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JSON_POJO_BUILDER_ANNOTATION, withPrefixMvp, buildMethodNameMvp); + + if (jacksonVersions.contains(JacksonVersion.TWO)) { + builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JacksonAnnotationType.JSON_POJO_BUILDER2.getQualifiednameAsCharArrayArray(), withPrefixMvp, buildMethodNameMvp); + } + if (jacksonVersions.contains(JacksonVersion.THREE)) { + builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JacksonAnnotationType.JSON_POJO_BUILDER3.getQualifiednameAsCharArrayArray(), withPrefixMvp, buildMethodNameMvp); + } // @SuperBuilder? Make it package-private! - if (superBuilderAnnotationNode != null) - builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate; + if (superBuilderAnnotationNode != null) 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) { + if (hasAnnotation(eclipseNode, JacksonAnnotationType.JSON_PROPERTY2) || + hasAnnotation(eclipseNode, JacksonAnnotationType.JSON_IGNORE2)) { + continue; + } + if (eclipseNode.isTransient()) { + createJsonIgnoreForField(eclipseNode, annotationNode); + } else { + createJsonPropertyForField(eclipseNode, annotationNode); + } + } + } + tdNode.rebuild(); + } + + private void createJsonPropertyForField(EclipseNode fieldNode, EclipseNode annotationNode) { + ASTNode astNode = fieldNode.get(); + if (astNode instanceof FieldDeclaration) { + FieldDeclaration fd = (FieldDeclaration) astNode; + StringLiteral fieldName = new StringLiteral(fd.name, 0, 0, 0); + fd.annotations = addAnnotation(fieldNode.get(), fd.annotations, JacksonAnnotationType.JSON_PROPERTY2.getQualifiednameAsCharArrayArray(), fieldName); + } + } + + private void createJsonIgnoreForField(EclipseNode fieldNode, EclipseNode annotationNode) { + ASTNode astNode = fieldNode.get(); + if (astNode instanceof FieldDeclaration) { + FieldDeclaration fd = (FieldDeclaration) astNode; + fd.annotations = addAnnotation(fieldNode.get(), fd.annotations, JacksonAnnotationType.JSON_IGNORE2.getQualifiednameAsCharArrayArray()); + } } private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, TypeDeclaration td, AnnotationValues builderAnnotation) { @@ -149,9 +224,8 @@ private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, E 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 +237,29 @@ private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, E } 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 List readConfiguredJacksonVersions(EclipseNode annotationNode) { + List jacksonVersions = annotationNode.getAst().readConfigurationOr(ConfigurationKeys.JACKSONIZED_JACKSON_VERSION, Arrays.asList()); + if (!jacksonVersions.isEmpty()) return jacksonVersions; + annotationNode.addWarning(JacksonVersion.AMBIGUOUS_JACKSON_VERSION_WARNING_TEXT); + return Arrays.asList(JacksonVersion.TWO); + } + 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/HandleLockedUtil.java b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java index 64fe35914b..fdfe40a6f4 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -1,3 +1,24 @@ +/* + * Copyright (C) 2024-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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /* Copyright (C) 2021-2023 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -66,6 +87,8 @@ public static void preHandle(String annotationValue, char[][] lockTypeClass, cha if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) return; MethodDeclaration method = (MethodDeclaration) methodNode.get(); if (method.isAbstract()) return; + EclipseNode typeNode = upToTypeNode(annotationNode); + if (isRecord(typeNode)) return; createLockField(annotationValue, annotationNode, lockTypeClass, lockImplClass, new AtomicBoolean(method.isStatic()), false); } @@ -117,7 +140,7 @@ private static char[] createLockField(String name, EclipseNode annotationNode, c lockAlloc.type = setGeneratedBy(new QualifiedTypeReference(lockImplClass, new long[] { 0, 0, 0, 0, 0 }), source); fieldDecl.type = setGeneratedBy(new QualifiedTypeReference(lockTypeClass, new long[] { 0, 0, 0, 0, 0 }), source); fieldDecl.initialization = lockAlloc; - injectField(annotationNode.up().up(), fieldDecl); + injectFieldAndMarkGenerated(annotationNode.up().up(), fieldDecl); } return lockName; @@ -197,6 +220,7 @@ public static void handle(String annotationValue, Annotation source, EclipseNode tryStatement.tryBlock = block; tryStatement.finallyBlock = new Block(0); tryStatement.finallyBlock.statements = new Statement[] { unLock }; + setGeneratedBy(tryStatement, source); method.statements = new Statement[] { acquireLock, tryStatement }; diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index db9104ef36..99bd874be6 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2021 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -39,6 +39,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; import lombok.core.configuration.IdentifierName; @@ -57,7 +58,7 @@ private HandleLog() { throw new UnsupportedOperationException(); } - public static void processAnnotation(LoggingFramework framework, AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + public static void processAnnotation(LoggingFramework framework, AccessLevel access, AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { EclipseNode owner = annotationNode.up(); switch (owner.getKind()) { @@ -97,6 +98,7 @@ public static void processAnnotation(LoggingFramework framework, AnnotationValue Expression loggerTopic = (Expression) annotation.getActualExpression("topic"); if (valueGuess instanceof String && ((String) valueGuess).trim().isEmpty()) loggerTopic = null; + if (framework.getDeclaration().getParametersWithTopic() == null && loggerTopic != null) { annotationNode.addError(framework.getAnnotationAsString() + " does not allow a topic."); loggerTopic = null; @@ -106,12 +108,12 @@ public static void processAnnotation(LoggingFramework framework, AnnotationValue loggerTopic = new StringLiteral(new char[]{}, 0, 0, 0); } + if (access == AccessLevel.NONE) break; + ClassLiteralAccess loggingType = selfType(owner, source); - FieldDeclaration fieldDeclaration = createField(framework, source, loggingType, logFieldName.getName(), useStatic, loggerTopic); + FieldDeclaration fieldDeclaration = createField(framework, access, source, loggingType, logFieldName.getName(), useStatic, loggerTopic); fieldDeclaration.traverse(new SetGeneratedByVisitor(source), typeDecl.staticInitializerScope); - // TODO temporary workaround for issue 290. https://github.com/projectlombok/lombok/issues/290 - // injectFieldSuppressWarnings(owner, fieldDeclaration); - injectField(owner, fieldDeclaration); + injectFieldAndMarkGenerated(owner, fieldDeclaration); owner.rebuild(); break; default: @@ -133,7 +135,7 @@ public static ClassLiteralAccess selfType(EclipseNode type, Annotation source) { return result; } - private static FieldDeclaration createField(LoggingFramework framework, Annotation source, ClassLiteralAccess loggingType, String logFieldName, boolean useStatic, Expression loggerTopic) { + private static FieldDeclaration createField(LoggingFramework framework, AccessLevel access, Annotation source, ClassLiteralAccess loggingType, String logFieldName, boolean useStatic, Expression loggerTopic) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; @@ -141,7 +143,7 @@ private static FieldDeclaration createField(LoggingFramework framework, Annotati FieldDeclaration fieldDecl = new FieldDeclaration(logFieldName.toCharArray(), 0, -1); setGeneratedBy(fieldDecl, source); fieldDecl.declarationSourceEnd = -1; - fieldDecl.modifiers = Modifier.PRIVATE | (useStatic ? Modifier.STATIC : 0) | Modifier.FINAL; + fieldDecl.modifiers = toEclipseModifier(access) | (useStatic ? Modifier.STATIC : 0) | Modifier.FINAL; LogDeclaration logDeclaration = framework.getDeclaration(); fieldDecl.type = createTypeReference(logDeclaration.getLoggerType().getName(), source); @@ -217,7 +219,7 @@ private static final Expression createFactoryTypeParameter(ClassLiteralAccess lo public static class HandleCommonsLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_COMMONS_FLAG_USAGE, "@apachecommons.CommonsLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.COMMONS, annotation, source, annotationNode); + processAnnotation(LoggingFramework.COMMONS, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -228,7 +230,7 @@ public static class HandleCommonsLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JUL_FLAG_USAGE, "@java.Log", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.JUL, annotation, source, annotationNode); + processAnnotation(LoggingFramework.JUL, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -239,7 +241,7 @@ public static class HandleJulLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_LOG4J_FLAG_USAGE, "@Log4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.LOG4J, annotation, source, annotationNode); + processAnnotation(LoggingFramework.LOG4J, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -250,7 +252,7 @@ public static class HandleLog4jLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_LOG4J2_FLAG_USAGE, "@Log4j2", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode); + processAnnotation(LoggingFramework.LOG4J2, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -261,7 +263,7 @@ public static class HandleLog4j2Log extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_SLF4J_FLAG_USAGE, "@Slf4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.SLF4J, annotation, source, annotationNode); + processAnnotation(LoggingFramework.SLF4J, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -272,7 +274,7 @@ public static class HandleSlf4jLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_XSLF4J_FLAG_USAGE, "@XSlf4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.XSLF4J, annotation, source, annotationNode); + processAnnotation(LoggingFramework.XSLF4J, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -283,7 +285,7 @@ public static class HandleXSlf4jLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JBOSSLOG_FLAG_USAGE, "@JBossLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.JBOSSLOG, annotation, source, annotationNode); + processAnnotation(LoggingFramework.JBOSSLOG, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -294,7 +296,7 @@ public static class HandleJBossLog extends EclipseAnnotationHandler { @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_FLOGGER_FLAG_USAGE, "@Flogger", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.FLOGGER, annotation, source, annotationNode); + processAnnotation(LoggingFramework.FLOGGER, annotation.getInstance().access(), annotation, source, annotationNode); } } @@ -311,7 +313,7 @@ public static class HandleCustomLog extends EclipseAnnotationHandler onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod", annotationNode); + if (!onMethod.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Setter(onMethod=...)"); + } List onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam", annotationNode); + if (!onParam.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Setter(onParam=...)"); + } switch (node.getKind()) { case FIELD: @@ -155,6 +163,7 @@ public void createSetterForField( AnnotationValues accessors = getAccessorsForField(fieldNode); String setterName = toSetterName(fieldNode, isBoolean, accessors); boolean shouldReturnThis = shouldReturnThis(fieldNode, accessors); + boolean fluent = accessors.isExplicit("fluent"); if (setterName == null) { fieldNode.addWarning("Not generating setter for this field: It does not fit your @Accessors prefix list."); @@ -181,11 +190,11 @@ public void createSetterForField( } } - MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), false, fieldNode, setterName, null, null, shouldReturnThis, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), false, fieldNode, setterName, null, null, shouldReturnThis, modifier, sourceNode, onMethod, onParam, fluent); injectMethod(fieldNode.up(), method); } - static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] paramName, char[] booleanFieldToSet, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List onMethod, List onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] paramName, char[] booleanFieldToSet, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List onMethod, List onParam, boolean fluent) { ASTNode source = sourceNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; @@ -198,11 +207,11 @@ static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, returnThis = new ReturnStatement(thisRef, pS, pE); } - MethodDeclaration d = createSetter(parent, deprecate, fieldNode, name, paramName, booleanFieldToSet, returnType, returnThis, modifier, sourceNode, onMethod, onParam); + MethodDeclaration d = createSetter(parent, deprecate, fieldNode, name, paramName, booleanFieldToSet, returnType, returnThis, modifier, sourceNode, onMethod, onParam, fluent); return d; } - static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] paramName, char[] booleanFieldToSet, TypeReference returnType, Statement returnStatement, int modifier, EclipseNode sourceNode, List onMethod, List onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] paramName, char[] booleanFieldToSet, TypeReference returnType, Statement returnStatement, int modifier, EclipseNode sourceNode, List onMethod, List onParam, boolean fluent) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); if (paramName == null) paramName = field.name; ASTNode source = sourceNode.get(); @@ -222,7 +231,8 @@ static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, if (isFieldDeprecated(fieldNode) || deprecate) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode)); + // Copying Jackson annotations is required for fluent accessors (otherwise Jackson would not find the accessor). + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode, fluent)); 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/HandleStandardException.java b/src/core/lombok/eclipse/handlers/HandleStandardException.java index def6e495e2..4672555cf8 100755 --- a/src/core/lombok/eclipse/handlers/HandleStandardException.java +++ b/src/core/lombok/eclipse/handlers/HandleStandardException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Project Lombok Authors. + * Copyright (C) 2021-2024 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 @@ -70,8 +70,10 @@ private void generateNoArgsConstructor(EclipseNode typeNode, AccessLevel level, if (hasConstructor(typeNode) != MemberExistsResult.NOT_EXISTS) return; int pS = source.get().sourceStart, pE = source.get().sourceEnd; + Expression messageArgument = new CastExpression(new NullLiteral(pS, pE), generateQualifiedTypeRef(source.get(), TypeConstants.JAVA_LANG_STRING)); + Expression causeArgument = new CastExpression(new NullLiteral(pS, pE), generateQualifiedTypeRef(source.get(), TypeConstants.JAVA_LANG_THROWABLE)); ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.This); - explicitCall.arguments = new Expression[] {new NullLiteral(pS, pE), new NullLiteral(pS, pE)}; + explicitCall.arguments = new Expression[] {messageArgument, causeArgument}; ConstructorDeclaration constructor = createConstructor(level, typeNode, false, false, source, explicitCall, null); injectMethod(typeNode, constructor); } @@ -81,8 +83,10 @@ private void generateMsgOnlyConstructor(EclipseNode typeNode, AccessLevel level, int pS = source.get().sourceStart, pE = source.get().sourceEnd; long p = (long) pS << 32 | pE; + Expression messageArgument = new SingleNameReference(MESSAGE, p); + Expression causeArgument = new CastExpression(new NullLiteral(pS, pE), generateQualifiedTypeRef(source.get(), TypeConstants.JAVA_LANG_THROWABLE)); ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.This); - explicitCall.arguments = new Expression[] {new SingleNameReference(MESSAGE, p), new NullLiteral(pS, pE)}; + explicitCall.arguments = new Expression[] {messageArgument, causeArgument}; ConstructorDeclaration constructor = createConstructor(level, typeNode, true, false, source, explicitCall, null); injectMethod(typeNode, constructor); } diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 82e561de52..8ed1fa5ac0 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 @@ -100,7 +100,7 @@ import lombok.eclipse.handlers.HandleBuilder.BuilderFieldData; import lombok.eclipse.handlers.HandleBuilder.BuilderJob; import lombok.experimental.NonFinal; -import lombok.experimental.SuperBuilder; +import lombok.SuperBuilder; import lombok.spi.Provides; @Provides @@ -151,7 +151,6 @@ void setBuilderToAbstract() { } @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); SuperBuilderJob job = new SuperBuilderJob(); job.sourceNode = annotationNode; job.source = ast; @@ -162,13 +161,9 @@ void setBuilderToAbstract() { 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 +338,7 @@ void setBuilderToAbstract() { 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 +400,7 @@ void setBuilderToAbstract() { } // 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 +584,7 @@ private void generateBuilderBasedConstructor(BuilderJob job, boolean callBuilder 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 +648,7 @@ private MethodDeclaration generateBuilderMethod(SuperBuilderJob job) { } /** - * 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 +687,10 @@ private MethodDeclaration generateToBuilderMethod(SuperBuilderJob job) {
 		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 +709,9 @@ private MethodDeclaration generateFillValuesMethod(SuperBuilderJob job, boolean
 		
 		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 +720,14 @@ private MethodDeclaration generateFillValuesMethod(SuperBuilderJob job, boolean
 			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 +738,9 @@ private MethodDeclaration generateFillValuesMethod(SuperBuilderJob job, boolean
 		
 		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,10 +998,10 @@ private void generateSimpleSetterMethodForBuilder(BuilderJob job, boolean deprec
 			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());
+			job.sourceNode, methodAnnsList, annosOnParam != null ? Arrays.asList(copyAnnotations(job.source, annosOnParam)) : Collections.emptyList(), false);
 		if (job.sourceNode.up().getKind() == Kind.METHOD) {
 			copyJavadocFromParam(originalFieldNode.up(), setter, td, paramName.toString());
 		} else {
@@ -1095,8 +1086,7 @@ private java.util.Set gatherUsedTypeNames(TypeParameter[] typeParams, Ty
 		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 +1109,12 @@ private java.util.Set gatherUsedTypeNames(TypeParameter[] typeParams, Ty
 		
 		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 +1215,7 @@ private boolean constructorExists(EclipseNode type, String builderClassName) {
 					// 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/HandleSynchronized.java b/src/core/lombok/eclipse/handlers/HandleSynchronized.java
index c5fbcf4fdc..087bc051fe 100644
--- a/src/core/lombok/eclipse/handlers/HandleSynchronized.java
+++ b/src/core/lombok/eclipse/handlers/HandleSynchronized.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
@@ -69,6 +69,8 @@ public class HandleSynchronized extends EclipseAnnotationHandler {
 		if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) return;
 		MethodDeclaration method = (MethodDeclaration) methodNode.get();
 		if (method.isAbstract()) return;
+		EclipseNode typeNode = upToTypeNode(annotationNode);
+		if (isRecord(typeNode)) return;
 		
 		createLockField(annotation, annotationNode, new boolean[] {method.isStatic()}, false);
 		
@@ -129,9 +131,7 @@ public char[] createLockField(AnnotationValues annotation, Eclipse
 			fieldDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 });
 			setGeneratedBy(fieldDecl.type, source);
 			fieldDecl.initialization = arrayAlloc;
-			// TODO temporary workaround for issue 290. https://github.com/projectlombok/lombok/issues/290
-			// injectFieldSuppressWarnings(annotationNode.up().up(), fieldDecl);
-			injectField(annotationNode.up().up(), fieldDecl);
+			injectFieldAndMarkGenerated(annotationNode.up().up(), fieldDecl);
 		}
 		
 		return lockName;
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
index a43e033188..85c7d60548 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 @@ void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate, TypeRe
 		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 810a7878ec..2802bf7aaf 100755
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
@@ -195,7 +195,7 @@ void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate, TypeRe
 		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 16ed2d44c5..eee849f2eb 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 @@ private void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate
 		
 		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 a432f3fe7d..be6d13f7b9 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 193bda0f3c..6fd3da90e1 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-2026 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,8 @@
  * The SuperBuilder annotation creates a so-called 'builder' aspect to the class that is annotated with {@code @SuperBuilder}, but which works well when extending.
  * It is similar to {@code @Builder}, except it is only legal on types, is less configurable, but allows you to {@code extends} other builder-able classes.
  * 

+ * NB: {@code @SuperBuilder} has been moved to the main package; update your lombok dependencies and search/replace all {@code import lombok.experimental.SuperBuilder} to {@code import lombok.SuperBuilder}. + *

* All classes in the hierarchy must be annotated with {@code @SuperBuilder}. *

* Lombok generates 2 inner 'builder' classes, which extend the parent class' builder class (unless your class doesn't have an extends clause). @@ -40,12 +42,13 @@ *

* 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. + * Complete documentation is found at the project lombok features page for @SuperBuilder. * * @see Singular */ +// @Deprecated // First release on or after 2027-06-01: Deprecate this type, with text: @deprecated {@code SuperBuilder} has been promoted to the main package; use {@link lombok.SuperBuilder} instead. @Target(TYPE) @Retention(SOURCE) public @interface SuperBuilder { @@ -56,8 +59,8 @@ 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 f828166056..81c01cec4c 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 @@ *

  • 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/experimental/Wither.java b/src/core/lombok/experimental/Wither.java deleted file mode 100644 index cf20c1ebbf..0000000000 --- a/src/core/lombok/experimental/Wither.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2012-2019 The Project Lombok Authors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package lombok.experimental; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import lombok.AccessLevel; - -/** - * Put on any field to make lombok build a 'wither' - a withX method which produces a clone of this object (except for 1 field which gets a new value). - *

    - * Complete documentation is found at the project lombok features page for @Wither. - *

    - * Even though it is not listed, this annotation also has the {@code onParam} and {@code onMethod} parameter. See the full documentation for more details. - *

    - * Example: - *

    - *     private @Wither final int foo;
    - * 
    - * - * will generate: - * - *
    - *     public SELF_TYPE withFoo(int foo) {
    - *         return this.foo == foo ? this : new SELF_TYPE(otherField1, otherField2, foo);
    - *     }
    - * 
    - *

    - * This annotation can also be applied to a class, in which case it'll be as if all non-static fields that don't already have - * a {@code Wither} annotation have the annotation. - * - * @deprecated {@link lombok.With} has been promoted to the main package, so use that one instead. - */ -@Target({ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.SOURCE) -public @interface Wither { - /** - * If you want your wither to be non-public, you can specify an alternate access level here. - * - * @return The method will be generated with this access modifier. - */ - AccessLevel value() default AccessLevel.PUBLIC; - - /** - * Any annotations listed here are put on the generated method. - * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).
    - * up to JDK7:
    - * {@code @Wither(onMethod=@__({@AnnotationsGoHere}))}
    - * from JDK8:
    - * {@code @Wither(onMethod_={@AnnotationsGohere})} // note the underscore after {@code onMethod}. - * - * @return List of annotations to apply to the generated method. - */ - AnyAnnotation[] onMethod() default {}; - - /** - * Any annotations listed here are put on the generated method's parameter. - * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).
    - * up to JDK7:
    - * {@code @Wither(onParam=@__({@AnnotationsGoHere}))}
    - * from JDK8:
    - * {@code @Wither(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}. - * - * @return List of annotations to apply to the generated parameter in the method. - */ - AnyAnnotation[] onParam() default {}; - - /** - * Placeholder annotation to enable the placement of annotations on the generated code. - * @deprecated Don't use this annotation, ever - Read the documentation. - */ - @Deprecated - @Retention(RetentionPolicy.SOURCE) - @Target({}) - @interface AnyAnnotation {} -} diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index 973f556fec..4ce5a6b7d6 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2017 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -62,6 +64,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface CommonsLog { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/flogger/Flogger.java b/src/core/lombok/extern/flogger/Flogger.java index 893032ed8c..81f216f98c 100644 --- a/src/core/lombok/extern/flogger/Flogger.java +++ b/src/core/lombok/extern/flogger/Flogger.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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -60,4 +62,11 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Flogger { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; } diff --git a/src/core/lombok/extern/jackson/Jacksonized.java b/src/core/lombok/extern/jackson/Jacksonized.java index 801ddbcbc3..d75b327f06 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,21 +28,30 @@ import java.lang.annotation.Target; import lombok.Builder; -import lombok.experimental.SuperBuilder; +import lombok.experimental.Accessors; +import lombok.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. *

    - * In particular, the annotation does the following: + * In particular, the annotation does the following for {@code @(Super)Builder}: *

      *
    • 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 @@ *
    • 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/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index 96e52191c0..1ecf3c077b 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2017 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -61,6 +63,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/jbosslog/JBossLog.java b/src/core/lombok/extern/jbosslog/JBossLog.java index a6e5e353cb..1c9165e50f 100644 --- a/src/core/lombok/extern/jbosslog/JBossLog.java +++ b/src/core/lombok/extern/jbosslog/JBossLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2017 The Project Lombok Authors. + * Copyright (C) 2016-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -61,6 +63,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface JBossLog { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 3db01029e5..0e421e9ddf 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2017 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -62,6 +64,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log4j { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java index 3e59a53213..9aea5a7aac 100644 --- a/src/core/lombok/extern/log4j/Log4j2.java +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2017 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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -62,6 +64,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log4j2 { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index da65e27c2c..bc8cf9fc11 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2017 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -61,6 +63,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Slf4j { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index 2dfef26576..0ddad2e49d 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 The Project Lombok Authors. + * Copyright (C) 2012-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 @@ -26,6 +26,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** * Causes lombok to generate a logger field. *

    @@ -61,6 +63,14 @@ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface XSlf4j { + /** + * Sets the access level of the generated log field. + * Default: {@code AccessLevel.PRIVATE}. + * + * @return The constructed Logger method will be generated with this access modifier. + */ + AccessLevel access() default AccessLevel.PRIVATE; + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/javac/CompilerMessageSuppressor.java b/src/core/lombok/javac/CompilerMessageSuppressor.java index 7d7b81e677..fc820b908e 100644 --- a/src/core/lombok/javac/CompilerMessageSuppressor.java +++ b/src/core/lombok/javac/CompilerMessageSuppressor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -24,7 +24,10 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; import java.util.LinkedList; import java.util.Map; import java.util.Queue; @@ -36,7 +39,6 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic; -import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import lombok.permit.Permit; @@ -53,9 +55,14 @@ public final class CompilerMessageSuppressor { private static final Field deferDiagnosticsField, deferredDiagnosticsField, diagnosticHandlerField; private static final ConcurrentMap, Field> handlerDeferredFields = new ConcurrentHashMap, Field>(); private static final Field NULL_FIELD; + private static final Class DIAGNOSTIC_HANDLER; + private static final Class DISCARD_DIAGNOSTIC_HANDLER; + private static final Method POP_DIAGNOSTIC_HANDLER; + private static final Constructor DISCARD_DIAGNOSTIC_HANDLER_CONSTRUCTOR; private Boolean dumpOnError, promptOnError; private DiagnosticListener contextDiagnosticListener, logDiagnosticListener; private final Context context; + private Object diagnosticHandler; private static final ThreadLocal> queueCache = new ThreadLocal>(); @@ -87,6 +94,11 @@ enum Writers { diagnosticHandlerField = getDeclaredField(Log.class, "diagnosticHandler"); NULL_FIELD = getDeclaredField(JavacResolution.class, "NULL_FIELD"); + + DIAGNOSTIC_HANDLER = getClass("com.sun.tools.javac.util.Log$DiagnosticHandler"); + DISCARD_DIAGNOSTIC_HANDLER = getClass("com.sun.tools.javac.util.Log$DiscardDiagnosticHandler"); + POP_DIAGNOSTIC_HANDLER = DIAGNOSTIC_HANDLER != null ? Permit.permissiveGetMethod(Log.class, "popDiagnosticHandler", DIAGNOSTIC_HANDLER) : null; + DISCARD_DIAGNOSTIC_HANDLER_CONSTRUCTOR = DISCARD_DIAGNOSTIC_HANDLER != null ? Permit.permissiveGetConstructor(DISCARD_DIAGNOSTIC_HANDLER, Log.class) : null; } static Field getDeclaredField(Class c, String fieldName) { @@ -97,6 +109,14 @@ static Field getDeclaredField(Class c, String fieldName) { } } + static Class getClass(String name) { + try { + return Class.forName(name); + } catch (Throwable t) { + return null; + } + } + public CompilerMessageSuppressor(Context context) { this.log = Log.instance(context); this.context = context; @@ -118,16 +138,6 @@ public void disableLoggers() { } } catch (Exception e) {} - if (diagnosticHandlerField != null) try { - Object handler = diagnosticHandlerField.get(log); - Field field = getDeferredField(handler); - if (field != null) { - queueCache.set((Queue) field.get(handler)); - Queue empty = new LinkedList(); - field.set(handler, empty); - } - } catch (Exception e) {} - if (dumpOnErrorField != null) try { dumpOnError = (Boolean) dumpOnErrorField.get(log); dumpOnErrorField.set(log, false); @@ -145,6 +155,11 @@ public void disableLoggers() { diagnosticListenerField.set(log, null); } catch (Exception e) { } + + if (DISCARD_DIAGNOSTIC_HANDLER != null) try { + diagnosticHandler = Permit.newInstance(DISCARD_DIAGNOSTIC_HANDLER_CONSTRUCTOR, log); + } catch (Exception e) { + } } private static Field getDeferredField(Object handler) { @@ -183,19 +198,15 @@ public void enableLoggers() { logDiagnosticListener = null; } catch (Exception e) {} - if (diagnosticHandlerField != null && queueCache.get() != null) try { - Object handler = diagnosticHandlerField.get(log); - Field field = getDeferredField(handler); - if (field != null) { - field.set(handler, queueCache.get()); - queueCache.set(null); - } - } catch (Exception e) {} - if (deferDiagnosticsField != null && queueCache.get() != null) try { deferredDiagnosticsField.set(log, queueCache.get()); queueCache.set(null); } catch (Exception e) {} + + if (diagnosticHandler != null) try { + Permit.invoke(POP_DIAGNOSTIC_HANDLER, log, diagnosticHandler); + } catch (Exception e) {} + diagnosticHandler = null; } public void removeAllBetween(JavaFileObject sourcefile, int startPos, int endPos) { @@ -222,11 +233,12 @@ public void removeAllBetween(JavaFileObject sourcefile, int startPos, int endPos if (field == null || receiver == null) return; try { - ListBuffer deferredDiagnostics = (ListBuffer) field.get(receiver); - ListBuffer newDeferredDiagnostics = new ListBuffer(); + Collection deferredDiagnostics = (Collection) field.get(receiver); + if (deferredDiagnostics.isEmpty()) return; + LinkedList newDeferredDiagnostics = new LinkedList(); for (Object diag_ : deferredDiagnostics) { if (!(diag_ instanceof JCDiagnostic)) { - newDeferredDiagnostics.append(diag_); + newDeferredDiagnostics.add(diag_); continue; } JCDiagnostic diag = (JCDiagnostic) diag_; @@ -234,7 +246,7 @@ public void removeAllBetween(JavaFileObject sourcefile, int startPos, int endPos if (here >= startPos && here < endPos && diag.getSource() == sourcefile) { // We eliminate it } else { - newDeferredDiagnostics.append(diag); + newDeferredDiagnostics.add(diag); } } field.set(receiver, newDeferredDiagnostics); diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 215a21101d..fc548bd1b1 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2020 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 @@ -41,6 +41,7 @@ import lombok.core.CleanupTask; import lombok.permit.Permit; +import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.code.Source; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.model.JavacElements; @@ -67,6 +68,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Position; /** * Wraps around javac's internal AST view to add useful features as well as the ability to visit parents from children, @@ -415,7 +417,6 @@ private static void initJcAnnotatedType(Class context) { JCANNOTATEDTYPE_FIELDS_INITIALIZED = true; } - private static Field JCENHANCEDFORLOOP_VARORRECORDPATTERN_FIELD = Permit.permissiveGetField(JCEnhancedForLoop.class, "varOrRecordPattern"); private static JCTree getVarOrRecordPattern(JCEnhancedForLoop loop) { if (JCENHANCEDFORLOOP_VARORRECORDPATTERN_FIELD == null) { @@ -581,9 +582,6 @@ void printMessage(Diagnostic.Kind kind, String message, JavacNode node, Diagnost case ERROR: errorLogger.error(pos, message); break; - case MANDATORY_WARNING: - errorLogger.mandatoryWarning(pos, message); - break; case WARNING: errorLogger.warning(pos, message); break; @@ -596,8 +594,9 @@ void printMessage(Diagnostic.Kind kind, String message, JavacNode node, Diagnost if (newSource != null) errorLogger.useSource(oldSource); } } - + public void removeFromDeferredDiagnostics(int startPos, int endPos) { + if (startPos == Position.NOPOS || endPos == Position.NOPOS) return; JCCompilationUnit self = (JCCompilationUnit) top().get(); new CompilerMessageSuppressor(getContext()).removeAllBetween(self.sourcefile, startPos, endPos); } @@ -643,29 +642,23 @@ private ErrorLog(Log log, Messager messager, Field errorCount, Field warningCoun this.errorCount = errorCount; this.warningCount = warningCount; } - + final JavaFileObject useSource(JavaFileObject file) { return log.useSource(file); } - + final void error(DiagnosticPosition pos, String message) { increment(errorCount); error1(pos, message); } - + final void warning(DiagnosticPosition pos, String message) { increment(warningCount); warning1(pos, message); } - - final void mandatoryWarning(DiagnosticPosition pos, String message) { - increment(warningCount); - mandatoryWarning1(pos, message); - } - + abstract void error1(DiagnosticPosition pos, String message); abstract void warning1(DiagnosticPosition pos, String message); - abstract void mandatoryWarning1(DiagnosticPosition pos, String message); abstract void note(DiagnosticPosition pos, String message); private void increment(Field field) { @@ -689,7 +682,6 @@ static ErrorLog create(Messager messager, Context context) { for (Field field : log.getClass().getFields()) { if (field.getName().equals("multipleErrors")) { hasMultipleErrors = true; - break; } } if (hasMultipleErrors) return new JdkBefore9(log, messager, errorCount); @@ -708,7 +700,7 @@ static class JdkBefore9 extends ErrorLog { private JdkBefore9(Log log, Messager messager, Field errorCount) { super(log, messager, errorCount, null); } - + @Override void error1(DiagnosticPosition pos, String message) { boolean prev = log.multipleErrors; log.multipleErrors = true; @@ -718,15 +710,11 @@ private JdkBefore9(Log log, Messager messager, Field errorCount) { log.multipleErrors = prev; } } - + @Override void warning1(DiagnosticPosition pos, String message) { log.warning(pos, "proc.messager", message); } - - @Override void mandatoryWarning1(DiagnosticPosition pos, String message) { - log.mandatoryWarning(pos, "proc.messager", message); - } - + @Override void note(DiagnosticPosition pos, String message) { log.note(pos, "proc.messager", message); } @@ -735,20 +723,20 @@ private JdkBefore9(Log log, Messager messager, Field errorCount) { static class Jdk9Plus extends ErrorLog { private static final String PROC_MESSAGER = "proc.messager"; private Object multiple; - private Method errorMethod, warningMethod, mandatoryWarningMethod, noteMethod; + private Method errorMethod, warningMethod, noteMethod; private Method errorKey, warningKey, noteKey; private JCDiagnostic.Factory diags; private Jdk9Plus(Log log, Messager messager, Field errorCount, Field warningCount) { super(log, messager, errorCount, warningCount); - + try { final String jcd = "com.sun.tools.javac.util.JCDiagnostic"; Class df = Class.forName(jcd + "$DiagnosticFlag"); for (Object constant : df.getEnumConstants()) { if (constant.toString().equals("MULTIPLE")) this.multiple = constant; } - + Class errorCls = Class.forName(jcd + "$Error"); Class warningCls = Class.forName(jcd + "$Warning"); Class noteCls = Class.forName(jcd + "$Note"); @@ -756,15 +744,17 @@ private Jdk9Plus(Log log, Messager messager, Field errorCount, Field warningCoun Class lc = log.getClass(); this.errorMethod = Permit.getMethod(lc, "error", df, DiagnosticPosition.class, errorCls); this.warningMethod = Permit.getMethod(lc, "warning", DiagnosticPosition.class, warningCls); - this.mandatoryWarningMethod = Permit.getMethod(lc, "mandatoryWarning", DiagnosticPosition.class, warningCls); this.noteMethod = Permit.getMethod(lc, "note", DiagnosticPosition.class, noteCls); - + Field diagsField = Permit.getField(lc.getSuperclass(), "diags"); this.diags = (JCDiagnostic.Factory) diagsField.get(log); - + Class dc = this.diags.getClass(); this.errorKey = Permit.getMethod(dc, "errorKey", String.class, Object[].class); - this.warningKey = Permit.getMethod(dc, "warningKey", String.class, Object[].class); + this.warningKey = Permit.permissiveGetMethod(dc, "warningKey", String.class, Object[].class); + if (warningKey == null) { + this.warningKey = Permit.getMethod(dc, "warningKey", LintCategory.class, String.class, Object[].class); + } this.noteKey = Permit.getMethod(dc, "noteKey", String.class, Object[].class); } catch (Throwable t) { //t.printStackTrace(); @@ -775,19 +765,18 @@ private Jdk9Plus(Log log, Messager messager, Field errorCount, Field warningCoun Object error = Permit.invokeSneaky(this.errorKey, diags, PROC_MESSAGER, new Object[] { message }); if (error != null) Permit.invokeSneaky(errorMethod, log, multiple, pos, error); } - + @Override void warning1(DiagnosticPosition pos, String message) { - Object warning = Permit.invokeSneaky(this.warningKey, diags, PROC_MESSAGER, new Object[] { message }); + Object warning; + if (this.warningKey.getParameterTypes().length == 3) { + warning = Permit.invokeSneaky(this.warningKey, diags, null, PROC_MESSAGER, new Object[] { message }); + } else { + warning = Permit.invokeSneaky(this.warningKey, diags, PROC_MESSAGER, new Object[] { message }); + } if (warning != null) Permit.invokeSneaky(warningMethod, log, pos, warning); } - - @Override - void mandatoryWarning1(DiagnosticPosition pos, String message) { - Object warning = Permit.invokeSneaky(this.warningKey, diags, PROC_MESSAGER, new Object[] { message }); - if (warning != null) Permit.invokeSneaky(mandatoryWarningMethod, log, pos, warning); - } - + @Override void note(DiagnosticPosition pos, String message) { Object note = Permit.invokeSneaky(this.noteKey, diags, PROC_MESSAGER, new Object[] { message }); diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 6ff6efe732..a8cdfca344 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -416,7 +416,7 @@ private static Iterable concat(final Type t, final Collection 0) winner = t; } if (winner == null) return createJavaLangObject(ast); return typeToJCTree(winner, ast, allowCompound, allowVoid, allowCapture); diff --git a/src/core/lombok/javac/apt/LombokProcessor.java b/src/core/lombok/javac/apt/LombokProcessor.java index 0467d7585b..c6e7c15e5f 100644 --- a/src/core/lombok/javac/apt/LombokProcessor.java +++ b/src/core/lombok/javac/apt/LombokProcessor.java @@ -56,6 +56,7 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Context.Key; import lombok.Lombok; import lombok.core.CleanupRegistry; @@ -171,17 +172,19 @@ private void placePostCompileAndDontMakeForceRoundDummiesHook() { Field filerFileManagerField = Permit.getField(JavacFiler.class, "fileManager"); filerFileManagerField.set(javacFiler, newFilerManager); - if (lombok.javac.Javac.getJavaCompilerVersion() > 8 - && !lombok.javac.handlers.JavacHandlerUtil.inNetbeansCompileOnSave(context)) { - replaceFileManagerJdk9(context, newFilerManager); - } + if (lombok.javac.handlers.JavacHandlerUtil.inNetbeansCompileOnSave(context)) return; + + replaceFileManagerJdk(context, newFilerManager); } } catch (Exception e) { throw Lombok.sneakyThrow(e); } } - private void replaceFileManagerJdk9(Context context, JavaFileManager newFiler) { + private void replaceFileManagerJdk(Context context, JavaFileManager newFiler) { + int v = lombok.javac.Javac.getJavaCompilerVersion(); + if (v < 9) return; + try { JavaCompiler compiler = (JavaCompiler) Permit.invoke(Permit.getMethod(JavaCompiler.class, "instance", Context.class), null, context); try { @@ -191,10 +194,8 @@ private void replaceFileManagerJdk9(Context context, JavaFileManager newFiler) { catch (Exception e) {} try { - Field writerField = Permit.getField(JavaCompiler.class, "writer"); - ClassWriter writer = (ClassWriter) writerField.get(compiler); - Field fileManagerField = Permit.getField(ClassWriter.class, "fileManager"); - Permit.set(fileManagerField, writer, newFiler); + if (v > 25) replaceFileManagerJdk26(context, compiler, newFiler); + else replaceFileManagerJdk9(context, compiler, newFiler); } catch (Exception e) {} } @@ -202,6 +203,26 @@ private void replaceFileManagerJdk9(Context context, JavaFileManager newFiler) { } } + private void replaceFileManagerJdk9(Context context, JavaCompiler compiler, JavaFileManager newFiler) throws NoSuchFieldException, IllegalAccessException { + Field writerField = Permit.getField(JavaCompiler.class, "writer"); + ClassWriter writer = (ClassWriter) writerField.get(compiler); + + Field fileManagerField = Permit.getField(ClassWriter.class, "fileManager"); + Permit.set(fileManagerField, writer, newFiler); + } + + private void replaceFileManagerJdk26(Context context, JavaCompiler compiler, JavaFileManager newFiler) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + Field writerField = Permit.getField(JavaCompiler.class, "writer"); + + // Setting final fields is... tricky on JDK26+ + @SuppressWarnings("unchecked") + Context.Key classWriterKey = (Key) Permit.getField(ClassWriter.class, "classWriterKey").get(null); + context.put(classWriterKey, (ClassWriter) null); + ClassWriter writer = ClassWriter.instance(context); + context.put(ClassWriter.class, writer); + writerField.set(compiler, writer); + } + private void forceMultipleRoundsInNetBeansEditor() { try { Field f = Permit.getField(JavacProcessingEnvironment.class, "isBackgroundCompilation"); diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index d814c05a93..1e023f20cf 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 @@ private void makePrefixedSetterMethodForBuilder(BuilderJob job, BuilderFieldData 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/HandleBuilderDefault.java b/src/core/lombok/javac/handlers/HandleBuilderDefault.java index 8438057abf..bf7435f50e 100644 --- a/src/core/lombok/javac/handlers/HandleBuilderDefault.java +++ b/src/core/lombok/javac/handlers/HandleBuilderDefault.java @@ -31,7 +31,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.SuperBuilder; +import lombok.SuperBuilder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.spi.Provides; @@ -44,7 +44,7 @@ public class HandleBuilderDefault extends JavacAnnotationHandler onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@NoArgsConstructor(onConstructor=...)"); + } NoArgsConstructor ann = annotation.getInstance(); AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; @@ -95,6 +99,9 @@ public static class HandleRequiredArgsConstructor extends JavacAnnotationHandler JavacNode typeNode = annotationNode.up(); if (!checkLegality(typeNode, annotationNode, NAME)) return; List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@RequiredArgsConstructor(onConstructor=...)"); + } RequiredArgsConstructor ann = annotation.getInstance(); AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; @@ -107,6 +114,33 @@ public static class HandleRequiredArgsConstructor extends JavacAnnotationHandler } } + @Provides + public static class HandleAllArgsConstructor extends JavacAnnotationHandler { + private static final String NAME = AllArgsConstructor.class.getSimpleName(); + private HandleConstructor handleConstructor = new HandleConstructor(); + + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); + + deleteAnnotationIfNeccessary(annotationNode, AllArgsConstructor.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); + JavacNode typeNode = annotationNode.up(); + if (!checkLegality(typeNode, annotationNode, NAME)) return; + List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); + if (!onConstructor.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@AllArgsConstructor(onConstructor=...)"); + } + AllArgsConstructor ann = annotation.getInstance(); + AccessLevel level = ann.access(); + if (level == AccessLevel.NONE) return; + String staticName = ann.staticName(); + if (annotation.isExplicit("suppressConstructorProperties")) { + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); + } + handleConstructor.generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); + } + } + public static List findRequiredFields(JavacNode typeNode) { return findFields(typeNode, true); } @@ -132,30 +166,6 @@ public static List findFields(JavacNode typeNode, boolean nullMarked) return fields.toList(); } - @Provides - public static class HandleAllArgsConstructor extends JavacAnnotationHandler { - private static final String NAME = AllArgsConstructor.class.getSimpleName(); - private HandleConstructor handleConstructor = new HandleConstructor(); - - @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { - handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); - - deleteAnnotationIfNeccessary(annotationNode, AllArgsConstructor.class); - deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); - JavacNode typeNode = annotationNode.up(); - if (!checkLegality(typeNode, annotationNode, NAME)) return; - List onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); - AllArgsConstructor ann = annotation.getInstance(); - AccessLevel level = ann.access(); - if (level == AccessLevel.NONE) return; - String staticName = ann.staticName(); - if (annotation.isExplicit("suppressConstructorProperties")) { - annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); - } - handleConstructor.generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); - } - } - public static List findAllFields(JavacNode typeNode) { return findAllFields(typeNode, false); } @@ -242,6 +252,7 @@ private void generate(JavacNode typeNode, AccessLevel level, List if (!(skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS)) { JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, source); + generateConstructorJavadoc(constr, typeNode, fields); injectMethod(typeNode, constr); } generateStaticConstructor(staticConstrRequired, typeNode, staticName, level, allToDefault, fields, source); @@ -250,6 +261,7 @@ private void generate(JavacNode typeNode, AccessLevel level, List private void generateStaticConstructor(boolean staticConstrRequired, JavacNode typeNode, String staticName, AccessLevel level, boolean allToDefault, List fields, JavacNode source) { if (staticConstrRequired) { JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, allToDefault ? List.nil() : fields, source); + generateConstructorJavadoc(staticConstr, typeNode, fields); injectMethod(typeNode, staticConstr); } } @@ -352,7 +364,7 @@ public static void addConstructorProperties(JCModifiers mods, JavacNode node, Li if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties(mods, typeNode, fieldsToParam); } - if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor)); + if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor, maker)); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source); @@ -465,4 +477,28 @@ public JCMethodDecl createStaticConstructor(String name, AccessLevel level, Java createRelevantNonNullAnnotation(typeNode, methodDef); return recursiveSetGeneratedBy(methodDef, source); } + + private void generateConstructorJavadoc(JCMethodDecl constructor, JavacNode typeNode, List fields) { + if (fields.isEmpty()) return; + + JCCompilationUnit cu = ((JCCompilationUnit) typeNode.top().get()); + String constructorJavadoc = getConstructorJavadocHeader(typeNode.getName()); + boolean fieldDescriptionAdded = false; + for (JavacNode fieldNode : fields) { + String paramName = removePrefixFromField(fieldNode).toString(); + String fieldJavadoc = getDocComment(cu, fieldNode.get()); + String paramJavadoc = getConstructorParameterJavadoc(paramName, fieldJavadoc); + + if (paramJavadoc == null) { + paramJavadoc = "@param " + paramName; + } else { + fieldDescriptionAdded = true; + } + + constructorJavadoc = addJavadocLine(constructorJavadoc, paramJavadoc); + } + if (fieldDescriptionAdded) { + setDocComment(cu, constructor, constructorJavadoc); + } + } } diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 4d72eea32c..96c22fa21f 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2021 The Project Lombok Authors. + * Copyright (C) 2010-2026 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,7 +25,6 @@ import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; import static lombok.javac.handlers.JavacHandlerUtil.*; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -109,8 +108,7 @@ public class HandleDelegate extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.DELEGATE_FLAG_USAGE, "@Delegate"); - @SuppressWarnings("deprecation") Class oldDelegate = lombok.Delegate.class; - deleteAnnotationIfNeccessary(annotationNode, Delegate.class, oldDelegate); + deleteAnnotationIfNeccessary(annotationNode, Delegate.class, "lombok.Delegate"); Type delegateType; Name delegateName = annotationNode.toName(annotationNode.up().getName()); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 259880fa2f..07610a0b69 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 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 @@ -185,12 +185,12 @@ public void generateMethods(JavacNode typeNode, JavacNode source, java.util.List } } - JCMethodDecl equalsMethod = createEquals(typeNode, members, callSuper, fieldAccess, needsCanEqual, source, onParam); + JCMethodDecl equalsMethod = createEquals(typeNode, members, callSuper, fieldAccess, needsCanEqual, source, copyAnnotations(onParam, typeNode.getTreeMaker())); injectMethod(typeNode, equalsMethod); if (needsCanEqual && canEqualExists == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl canEqualMethod = createCanEqual(typeNode, source, copyAnnotations(onParam)); + JCMethodDecl canEqualMethod = createCanEqual(typeNode, source, copyAnnotations(onParam, typeNode.getTreeMaker())); injectMethod(typeNode, canEqualMethod); } diff --git a/src/core/lombok/javac/handlers/HandleExtensionMethod.java b/src/core/lombok/javac/handlers/HandleExtensionMethod.java index 63b65baca5..6e2f6a30d0 100644 --- a/src/core/lombok/javac/handlers/HandleExtensionMethod.java +++ b/src/core/lombok/javac/handlers/HandleExtensionMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 The Project Lombok Authors. + * Copyright (C) 2012-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,8 +27,10 @@ import static lombok.javac.handlers.JavacResolver.*; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.lang.model.element.ElementKind; @@ -139,11 +141,18 @@ private static class ExtensionMethodReplaceVisitor extends TreeScanner extensions; final boolean suppressBaseMethods; + final Set names = new HashSet(); public ExtensionMethodReplaceVisitor(JavacNode annotationNode, List extensions, boolean suppressBaseMethods) { this.annotationNode = annotationNode; this.extensions = extensions; this.suppressBaseMethods = suppressBaseMethods; + + for (Extension extension : extensions) { + for (MethodSymbol methodSymbol : extension.extensionMethods) { + names.add(methodSymbol.name.toString()); + } + } } public void replace() { @@ -173,6 +182,7 @@ private void handleMethodCall(final JCMethodInvocation methodCall) { JCExpression receiver = receiverOf(methodCall); String methodName = methodNameOf(methodCall); + if (!names.contains(methodName)) return; if ("this".equals(receiver.toString()) || "this".equals(methodName) || "super".equals(methodName)) return; Map resolution = new JavacResolution(methodCallNode.getContext()).resolveMethodMember(methodCallNode); diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 153a220134..f6b28ffbe7 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 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 @@ -144,6 +144,9 @@ public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos, if (node == null) return; List onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode); + if (!onMethod.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Getter(onMethod=...)"); + } switch (node.getKind()) { case FIELD: @@ -215,7 +218,7 @@ public void createGetterForField(AccessLevel level, long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC); - injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker(), source, lazy, onMethod)); + injectMethod(fieldNode.up(), source, createGetter(access, fieldNode, fieldNode.getTreeMaker(), source, lazy, onMethod)); } public JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker treeMaker, JavacNode source, boolean lazy, List onMethod) { @@ -246,8 +249,15 @@ public JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker tr 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).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 { @@ -358,7 +368,7 @@ public List createLazyGetterBody(JavacTreeMaker maker, JavacNode fi } if (copyOfBoxedFieldType == null) copyOfBoxedFieldType = copyType(maker, field, source); - Name valueName = fieldNode.toName("value"); + Name valueName = fieldNode.toName("$value"); Name actualValueName = fieldNode.toName("actualValue"); /* java.lang.Object value = this.fieldName.get();*/ { diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java index 3ee0eec4e0..b9372bae34 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-2026 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 @@ -24,6 +24,9 @@ import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; import static lombok.javac.handlers.JavacHandlerUtil.*; +import java.util.Arrays; +import java.util.Collection; + import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; @@ -33,6 +36,7 @@ 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; @@ -41,8 +45,11 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; +import lombok.core.JacksonAnnotationType; +import lombok.core.configuration.JacksonVersion; import lombok.core.handlers.HandlerUtil; -import lombok.experimental.SuperBuilder; +import lombok.experimental.Accessors; +import lombok.SuperBuilder; import lombok.extern.jackson.Jacksonized; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; @@ -55,34 +62,99 @@ * 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 { + static JCExpression chainDots(JavacNode node, JacksonAnnotationType annotation) { + return JavacHandlerUtil.chainDots(node, annotation.getQualifiedNameAsStringArray()); + } + + static boolean hasAnnotation(JavacNode node, JacksonAnnotationType annotation) { + return JavacHandlerUtil.hasAnnotation(annotation.getQualifiedName(), node); + } @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 non-transient fields. It will be automatically copied to the getter/setters later. + // Add @JsonIgnore to all transient fields. It will be automatically copied to the getter/setters later. + readConfiguredJacksonVersions(annotationNode); + for (JavacNode javacNode : tdNode.down()) { + if (javacNode.getKind() == Kind.FIELD) { + if (hasAnnotation(javacNode, JacksonAnnotationType.JSON_PROPERTY2) || + hasAnnotation(javacNode, JacksonAnnotationType.JSON_IGNORE2)) { + continue; + } + if (javacNode.isTransient()) { + createJsonIgnoreForField(javacNode, annotationNode); + } else { + createJsonPropertyForField(javacNode, annotationNode); + } + } + } + } + + private void createJsonPropertyForField(JavacNode fieldNode, JavacNode annotationNode) { + JavacTreeMaker maker = fieldNode.getTreeMaker(); + List args = List.of(maker.Literal(fieldNode.getName())); + addFieldAnnotation(fieldNode, annotationNode, JacksonAnnotationType.JSON_PROPERTY2, args); + } + + private void createJsonIgnoreForField(JavacNode fieldNode, JavacNode annotationNode) { + addFieldAnnotation(fieldNode, annotationNode, JacksonAnnotationType.JSON_IGNORE2, List.nil()); + } + + private void addFieldAnnotation(JavacNode fieldNode, JavacNode annotationNode, JacksonAnnotationType annotationType, List args) { + JavacTreeMaker maker = fieldNode.getTreeMaker(); + JCExpression type = chainDots(fieldNode, annotationType); + JCAnnotation annotation = maker.Annotation(type, args); + recursiveSetGeneratedBy(annotation, annotationNode); + JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); + fieldDecl.mods.annotations = fieldDecl.mods.annotations.append(annotation); + } + + 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 +162,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)) { @@ -121,48 +191,68 @@ public class HandleJacksonized extends JavacAnnotationHandler { return; } + Collection jacksonVersions = readConfiguredJacksonVersions(annotationNode); + // Insert @JsonDeserialize on annotated class. - if (hasAnnotation("com.fasterxml.jackson.databind.annotation.JsonDeserialize", tdNode)) { + if (hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE2) || hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE3)) { annotationNode.addError("@JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson."); return; } - JCExpression jsonDeserializeType = chainDots(annotatedNode, "com", "fasterxml", "jackson", "databind", "annotation", "JsonDeserialize"); - JCExpression builderClassExpression = namePlusTypeParamsToTypeReference(maker, tdNode, annotationNode.toName(builderClassName), false, List.nil()); - JCFieldAccess builderClassReference = maker.Select(builderClassExpression, annotatedNode.toName("class")); - JCExpression assign = maker.Assign(maker.Ident(annotationNode.toName("builder")), builderClassReference); - JCAnnotation annotationJsonDeserialize = maker.Annotation(jsonDeserializeType, List.of(assign)); - recursiveSetGeneratedBy(annotationJsonDeserialize, annotationNode); - td.mods.annotations = td.mods.annotations.append(annotationJsonDeserialize); + + if (jacksonVersions.contains(JacksonVersion.TWO)) { + JCExpression jsonDeserializeType = chainDots(annotatedNode, JacksonAnnotationType.JSON_DESERIALIZE2); + insertJsonDeserializeAnnotation(annotationNode, annotatedNode, tdNode, td, maker, builderClassName, jsonDeserializeType); + } + if (jacksonVersions.contains(JacksonVersion.THREE)) { + JCExpression jsonDeserializeType = chainDots(annotatedNode, JacksonAnnotationType.JSON_DESERIALIZE3); + insertJsonDeserializeAnnotation(annotationNode, annotatedNode, tdNode, td, maker, builderClassName, jsonDeserializeType); + } // Copy annotations from the class to the builder class. List copyableAnnotations = findJacksonAnnotationsOnClass(tdNode); - List copiedAnnotations = copyAnnotations(copyableAnnotations); + List copiedAnnotations = copyAnnotations(copyableAnnotations, maker); for (JCAnnotation anno : copiedAnnotations) { recursiveSetGeneratedBy(anno, annotationNode); } builderClass.mods.annotations = builderClass.mods.annotations.appendList(copiedAnnotations); - // Insert @JsonPOJOBuilder on the builder class. - JCExpression jsonPOJOBuilderType = chainDots(annotatedNode, "com", "fasterxml", "jackson", "databind", "annotation", "JsonPOJOBuilder"); + if (jacksonVersions.contains(JacksonVersion.TWO)) { + JCExpression jsonPojoBuilderType = chainDots(annotatedNode, JacksonAnnotationType.JSON_POJO_BUILDER2); + insertJsonPojoAnnotation(annotationNode, annotatedNode, setPrefix, buildMethodName, maker, builderClass, jsonPojoBuilderType); + } + if (jacksonVersions.contains(JacksonVersion.THREE)) { + JCExpression jsonPojoBuilderType = chainDots(annotatedNode, JacksonAnnotationType.JSON_POJO_BUILDER3); + insertJsonPojoAnnotation(annotationNode, annotatedNode, setPrefix, buildMethodName, maker, builderClass, jsonPojoBuilderType); + } + // @SuperBuilder? Make it package-private! + if (superBuilderAnnotationNode != null) builderClass.mods.flags = builderClass.mods.flags & ~Flags.PRIVATE; + } + + // Insert @JsonPojoBuilder on the builder class. + private void insertJsonPojoAnnotation(JavacNode annotationNode, JavacNode annotatedNode, String setPrefix, String buildMethodName, JavacTreeMaker maker, JCClassDecl builderClass, JCExpression jsonPojoBuilderType) { JCExpression withPrefixExpr = maker.Assign(maker.Ident(annotationNode.toName("withPrefix")), maker.Literal(setPrefix)); JCExpression buildMethodNameExpr = maker.Assign(maker.Ident(annotationNode.toName("buildMethodName")), maker.Literal(buildMethodName)); - 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; - } - + JCAnnotation annotationJsonPojoBuilder = maker.Annotation(jsonPojoBuilderType, List.of(withPrefixExpr, buildMethodNameExpr)); + recursiveSetGeneratedBy(annotationJsonPojoBuilder, annotatedNode); + builderClass.mods.annotations = builderClass.mods.annotations.append(annotationJsonPojoBuilder); + } + + // Insert @JsonDeserialize on the class. + private void insertJsonDeserializeAnnotation(JavacNode annotationNode, JavacNode annotatedNode, JavacNode tdNode, JCClassDecl td, JavacTreeMaker maker, String builderClassName, JCExpression jsonDeserializeType) { + JCExpression builderClassExpression = namePlusTypeParamsToTypeReference(maker, tdNode, annotationNode.toName(builderClassName), false, List.nil()); + JCFieldAccess builderClassReference = maker.Select(builderClassExpression, annotatedNode.toName("class")); + JCExpression assign = maker.Assign(maker.Ident(annotationNode.toName("builder")), builderClassReference); + JCAnnotation annotationJsonDeserialize = maker.Annotation(jsonDeserializeType, List.of(assign)); + recursiveSetGeneratedBy(annotationJsonDeserialize, annotationNode); + td.mods.annotations = td.mods.annotations.append(annotationJsonDeserialize); + } + 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,19 +269,25 @@ private String getBuilderClassName(JavacNode annotationNode, JavacNode annotated } 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; } + private Collection readConfiguredJacksonVersions(JavacNode annotationNode) { + Collection jacksonVersions = annotationNode.getAst().readConfigurationOr(ConfigurationKeys.JACKSONIZED_JACKSON_VERSION, Arrays.asList()); + if (!jacksonVersions.isEmpty()) return jacksonVersions; + annotationNode.addWarning(JacksonVersion.AMBIGUOUS_JACKSON_VERSION_WARNING_TEXT); + return Arrays.asList(JacksonVersion.TWO); + } + private static List findJacksonAnnotationsOnClass(JavacNode node) { ListBuffer result = new ListBuffer(); 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/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index f5eefaa1b8..9dca9f48fb 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2021 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -21,10 +21,21 @@ */ package lombok.javac.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; import static lombok.javac.Javac.CTC_BOT; import static lombok.javac.handlers.JavacHandlerUtil.*; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; + +import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; import lombok.core.configuration.IdentifierName; @@ -38,17 +49,6 @@ import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import lombok.spi.Provides; -import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.tree.JCTree.JCAnnotation; -import com.sun.tools.javac.tree.JCTree.JCClassDecl; -import com.sun.tools.javac.tree.JCTree.JCExpression; -import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCLiteral; -import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import com.sun.tools.javac.tree.JCTree.JCVariableDecl; -import com.sun.tools.javac.util.List; -import com.sun.tools.javac.util.Name; - public class HandleLog { private static final IdentifierName LOG = IdentifierName.valueOf("log"); @@ -56,7 +56,7 @@ private HandleLog() { throw new UnsupportedOperationException(); } - public static void processAnnotation(LoggingFramework framework, AnnotationValues annotation, JavacNode annotationNode) { + public static void processAnnotation(LoggingFramework framework, AccessLevel access, AnnotationValues annotation, JavacNode annotationNode) { deleteAnnotationIfNeccessary(annotationNode, framework.getAnnotationClass()); JavacNode typeNode = annotationNode.up(); @@ -99,8 +99,10 @@ public static void processAnnotation(LoggingFramework framework, AnnotationValue loggerTopic = typeNode.getTreeMaker().Literal(""); } + if (access == AccessLevel.NONE) break; + JCFieldAccess loggingType = selfType(typeNode); - createField(framework, typeNode, loggingType, annotationNode, logFieldName.getName(), useStatic, loggerTopic); + createField(framework, access, typeNode, loggingType, annotationNode, logFieldName.getName(), useStatic, loggerTopic); break; default: annotationNode.addError("@Log is legal only on types."); @@ -114,7 +116,8 @@ public static JCFieldAccess selfType(JavacNode typeNode) { return maker.Select(maker.Ident(name), typeNode.toName("class")); } - private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JavacNode source, String logFieldName, boolean useStatic, JCExpression loggerTopic) { + + private static boolean createField(LoggingFramework framework, AccessLevel access, JavacNode typeNode, JCFieldAccess loggingType, JavacNode source, String logFieldName, boolean useStatic, JCExpression loggerTopic) { JavacTreeMaker maker = typeNode.getTreeMaker(); LogDeclaration logDeclaration = framework.getDeclaration(); @@ -127,11 +130,11 @@ private static boolean createField(LoggingFramework framework, JavacNode typeNod JCMethodInvocation factoryMethodCall = maker.Apply(List.nil(), factoryMethod, List.from(factoryParameters)); JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( - maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), + maker.Modifiers(toJavacModifier(access) | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), typeNode.toName(logFieldName), loggerType, factoryMethodCall), source); if (isRecord(typeNode) && Javac.getJavaCompilerVersion() < 16) { - // This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8243057 + // This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8243057 - note that the workaround is only for static fields in records, but our infra _requires_ `static` for logs in records (think about it). injectField(typeNode, fieldDecl); } else { @@ -156,11 +159,9 @@ private static JCExpression[] createFactoryParameters(JavacNode typeNode, JCFiel expressions[i] = maker.Apply(List.nil(), method, List.nil()); break; case TOPIC: - if (loggerTopic instanceof JCLiteral) { - expressions[i] = maker.Literal(((JCLiteral) loggerTopic).value); - } else { - expressions[i] = cloneType(maker, loggerTopic, typeNode); - } + JCExpression topicExpression = copyExpression(loggerTopic, maker); + recursiveSetGeneratedBy(topicExpression, typeNode); + expressions[i] = topicExpression; break; case NULL: expressions[i] = maker.Literal(CTC_BOT, null); @@ -180,7 +181,7 @@ private static JCExpression[] createFactoryParameters(JavacNode typeNode, JCFiel public static class HandleCommonsLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_COMMONS_FLAG_USAGE, "@apachecommons.CommonsLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.COMMONS, annotation, annotationNode); + processAnnotation(LoggingFramework.COMMONS, annotation.getInstance().access(), annotation, annotationNode); } } @@ -191,7 +192,7 @@ public static class HandleCommonsLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JUL_FLAG_USAGE, "@java.Log", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.JUL, annotation, annotationNode); + processAnnotation(LoggingFramework.JUL, annotation.getInstance().access(), annotation, annotationNode); } } @@ -202,7 +203,7 @@ public static class HandleJulLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_LOG4J_FLAG_USAGE, "@Log4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.LOG4J, annotation, annotationNode); + processAnnotation(LoggingFramework.LOG4J, annotation.getInstance().access(), annotation, annotationNode); } } @@ -213,7 +214,7 @@ public static class HandleLog4jLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_LOG4J2_FLAG_USAGE, "@Log4j2", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.LOG4J2, annotation, annotationNode); + processAnnotation(LoggingFramework.LOG4J2, annotation.getInstance().access(), annotation, annotationNode); } } @@ -224,7 +225,7 @@ public static class HandleLog4j2Log extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_SLF4J_FLAG_USAGE, "@Slf4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.SLF4J, annotation, annotationNode); + processAnnotation(LoggingFramework.SLF4J, annotation.getInstance().access(), annotation, annotationNode); } } @@ -235,7 +236,7 @@ public static class HandleSlf4jLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_XSLF4J_FLAG_USAGE, "@XSlf4j", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.XSLF4J, annotation, annotationNode); + processAnnotation(LoggingFramework.XSLF4J, annotation.getInstance().access(), annotation, annotationNode); } } @@ -246,7 +247,7 @@ public static class HandleXSlf4jLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JBOSSLOG_FLAG_USAGE, "@JBossLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.JBOSSLOG, annotation, annotationNode); + processAnnotation(LoggingFramework.JBOSSLOG, annotation.getInstance().access(), annotation, annotationNode); } } @@ -257,7 +258,7 @@ public static class HandleJBossLog extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.LOG_FLOGGER_FLAG_USAGE, "@Flogger", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); - processAnnotation(LoggingFramework.FLOGGER, annotation, annotationNode); + processAnnotation(LoggingFramework.FLOGGER, annotation.getInstance().access(), annotation, annotationNode); } } @@ -274,7 +275,7 @@ public static class HandleCustomLog extends JavacAnnotationHandlernil()); JCBlock body = maker.Block(0L, List.nil()); - if (existingCtr == null) { - JCMethodDecl constr = maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), body, null); - return recursiveSetGeneratedBy(constr, source); + JCMethodDecl constuctor = existingCtr; + if (constuctor == null) { + constuctor = maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), body, null); } else { - existingCtr.mods = mods; - existingCtr.body = body; - existingCtr = recursiveSetGeneratedBy(existingCtr, source); - addSuppressWarningsAll(existingCtr.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(existingCtr)), typeNode.getContext()); - addGenerated(existingCtr.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(existingCtr)), typeNode.getContext()); - return existingCtr; + constuctor.mods = mods; + constuctor.body = body; } + recursiveSetGeneratedBy(constuctor, source); + addSuppressWarningsAll(constuctor.mods, typeNode, source, typeNode.getContext()); + addGenerated(constuctor.mods, typeNode, source, typeNode.getContext()); + return constuctor; } /** @@ -132,7 +132,6 @@ private List addCompactConstructorIfNeeded(JavacNode typeNode, Jav if (md.name.contentEquals("")) { if ((md.mods.flags & Flags.GENERATEDCONSTR) != 0) { existingCtr = md; - existingCtr.mods.flags = existingCtr.mods.flags & ~Flags.GENERATEDCONSTR; generateConstructor = true; } else { if (!isTolerate(typeNode, md)) { @@ -307,7 +306,7 @@ public String returnVarNameIfNullCheck(JCStatement stat) { if (invocation.args.isEmpty()) return null; JCExpression firstArgument = invocation.args.head; if (!(firstArgument instanceof JCIdent)) return null; - return ((JCIdent) firstArgument).toString(); + return firstArgument.toString(); } if (isIf) { diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 04fa8b771a..0764c7685f 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 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 @@ -123,7 +123,13 @@ public void generateSetterForField(JavacNode fieldNode, JavacNode sourceNode, Ac if (level == AccessLevel.NONE || node == null) return; List onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod", annotationNode); + if (!onMethod.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Setter(onMethod=...)"); + } List onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam", annotationNode); + if (!onParam.isEmpty()) { + handleFlagUsage(annotationNode, ConfigurationKeys.ON_X_FLAG_USAGE, "@Setter(onParam=...)"); + } switch (node.getKind()) { case FIELD: @@ -205,7 +211,7 @@ public static JCMethodDecl createSetter(long access, boolean deprecate, JavacNod 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) { @@ -235,7 +241,7 @@ public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, List copyableAnnotations = findCopyableAnnotations(field); Name methodName = field.toName(setterName); - List annsOnParam = copyAnnotations(onParam).appendList(copyableAnnotations); + List annsOnParam = copyAnnotations(onParam, treeMaker).appendList(copyableAnnotations); long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, field.getContext()); JCExpression pType = cloneType(treeMaker, fieldDecl.vartype, source); @@ -268,12 +274,17 @@ public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, List throwsClauses = List.nil(); JCExpression annotationMethodDefaultValue = null; - List annsOnMethod = mergeAnnotations(copyAnnotations(onMethod), 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/HandleStandardException.java b/src/core/lombok/javac/handlers/HandleStandardException.java index dd764233c2..56bf8905e8 100644 --- a/src/core/lombok/javac/handlers/HandleStandardException.java +++ b/src/core/lombok/javac/handlers/HandleStandardException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Project Lombok Authors. + * Copyright (C) 2021-2024 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 @@ -81,7 +81,9 @@ private void generateNoArgsConstructor(JavacNode typeNode, AccessLevel level, Ja if (hasConstructor(typeNode) != MemberExistsResult.NOT_EXISTS) return; JavacTreeMaker maker = typeNode.getTreeMaker(); - List args = List.of(maker.Literal(CTC_BOT, null), maker.Literal(CTC_BOT, null)); + JCExpression stringArgument = maker.TypeCast(genJavaLangTypeRef(typeNode, "String"), maker.Literal(CTC_BOT, null)); + JCExpression throwableArgument = maker.TypeCast(genJavaLangTypeRef(typeNode, "Throwable"), maker.Literal(CTC_BOT, null)); + List args = List.of(stringArgument, throwableArgument); JCStatement thisCall = maker.Exec(maker.Apply(List.nil(), maker.Ident(typeNode.toName("this")), args)); JCMethodDecl constr = createConstructor(level, typeNode, false, false, source, List.of(thisCall)); injectMethod(typeNode, constr); @@ -91,7 +93,9 @@ private void generateMsgOnlyConstructor(JavacNode typeNode, AccessLevel level, J if (hasConstructor(typeNode, String.class) != MemberExistsResult.NOT_EXISTS) return; JavacTreeMaker maker = typeNode.getTreeMaker(); - List args = List.of(maker.Ident(typeNode.toName("message")), maker.Literal(CTC_BOT, null)); + JCExpression stringArgument = maker.Ident(typeNode.toName("message")); + JCExpression throwableArgument = maker.TypeCast(genJavaLangTypeRef(typeNode, "Throwable"), maker.Literal(CTC_BOT, null)); + List args = List.of(stringArgument, throwableArgument); JCStatement thisCall = maker.Exec(maker.Apply(List.nil(), maker.Ident(typeNode.toName("this")), args)); JCMethodDecl constr = createConstructor(level, typeNode, true, false, source, List.of(thisCall)); injectMethod(typeNode, constr); diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index d64daf385a..c5a50508fd 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 @@ -36,6 +36,7 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssign; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; @@ -63,14 +64,14 @@ import lombok.Singular; import lombok.ToString; import lombok.core.AST.Kind; -import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; +import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.handlers.HandlerUtil; import lombok.core.handlers.HandlerUtil.FieldAccess; import lombok.core.handlers.InclusionExclusionUtils.Included; import lombok.experimental.NonFinal; -import lombok.experimental.SuperBuilder; +import lombok.SuperBuilder; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; @@ -78,6 +79,7 @@ import lombok.javac.handlers.HandleBuilder.BuilderFieldData; import lombok.javac.handlers.HandleBuilder.BuilderJob; import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; +import lombok.javac.handlers.JavacHandlerUtil.JCAnnotatedTypeReflect; import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; @@ -130,7 +132,6 @@ void setBuilderToAbstract() { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); SuperBuilderJob job = new SuperBuilderJob(); job.sourceNode = annotationNode; job.checkerFramework = getCheckerFrameworkVersion(annotationNode); @@ -141,13 +142,10 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, 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. @@ -311,9 +309,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, 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; } } @@ -387,7 +383,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, } 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); @@ -513,7 +509,7 @@ private JavacNode generateBuilderImplClass(SuperBuilderJob job) { 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. @@ -580,8 +576,8 @@ private void generateBuilderBasedConstructor(SuperBuilderJob job, boolean callBu 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)); } @@ -622,7 +618,9 @@ private JCMethodDecl generateBuilderMethod(SuperBuilderJob job) { } /** - * 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);
    @@ -655,10 +653,11 @@ private JCMethodDecl generateToBuilderMethod(SuperBuilderJob job) {
     		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);
    @@ -680,7 +679,7 @@ private JCMethodDecl generateFillValuesMethod(SuperBuilderJob job, boolean inher
     		
     		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) {
    @@ -704,11 +703,12 @@ private JCMethodDecl generateFillValuesMethod(SuperBuilderJob job, boolean inher
     		
     		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) {
    @@ -725,7 +725,7 @@ private JCMethodDecl generateStaticFillValuesMethod(SuperBuilderJob job, String
     		
     		// 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);
    @@ -798,7 +798,7 @@ private JCMethodDecl generateAbstractSelfMethod(SuperBuilderJob job, boolean ove
     		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);
     	}
     	
    @@ -963,7 +963,7 @@ private void generateSimpleSetterMethodForBuilder(SuperBuilderJob job, boolean d
     		
     		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);
    @@ -1044,8 +1044,7 @@ private java.util.HashSet gatherUsedTypeNames(List type
     		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());
    @@ -1054,16 +1053,13 @@ private java.util.HashSet gatherUsedTypeNames(List type
     		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;
     	}
    @@ -1076,7 +1072,7 @@ private void addFirstToken(java.util.Set usedNames, JCTree type) {
     		}
     		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());
     	}
    @@ -1101,20 +1097,22 @@ private ListBuffer getTypeParamExpressions(List
     		ListBuffer typeParamsForBuilderParameter = new ListBuffer();
     		for (JCTree typeParam : typeParams) {
     			if (typeParam instanceof JCTypeParameter) {
    -				typeParamsForBuilderParameter.append(maker.Ident(((JCTypeParameter)typeParam).getName()));
    +				typeParamsForBuilderParameter.append(maker.Ident(((JCTypeParameter) typeParam).getName()));
     			} else if (typeParam instanceof JCIdent) {
    -				typeParamsForBuilderParameter.append(maker.Ident(((JCIdent)typeParam).getName()));
    +				typeParamsForBuilderParameter.append(maker.Ident(((JCIdent) typeParam).getName()));
     			} else if (typeParam instanceof JCFieldAccess) {
     				typeParamsForBuilderParameter.append(copySelect(maker, (JCFieldAccess) typeParam));
     			} else if (typeParam instanceof JCTypeApply) {
    -				typeParamsForBuilderParameter.append(cloneType(maker, (JCTypeApply)typeParam, source));
    +				typeParamsForBuilderParameter.append(cloneType(maker, (JCTypeApply) typeParam, source));
    +			} else if (typeParam instanceof JCArrayTypeTree) {
    +				typeParamsForBuilderParameter.append(cloneType(maker, (JCArrayTypeTree) typeParam, source));
     			} else if (JCAnnotatedTypeReflect.is(typeParam)) {
    -				typeParamsForBuilderParameter.append(cloneType(maker, (JCExpression)typeParam, source));
    +				typeParamsForBuilderParameter.append(cloneType(maker, (JCExpression) typeParam, source));
     			}
     		}
     		return typeParamsForBuilderParameter;
     	}
    -
    +	
     	private JCExpression copySelect(JavacTreeMaker maker, JCFieldAccess typeParam) {
     		java.util.List chainNames = new ArrayList();
     		JCExpression expression = typeParam;
    @@ -1127,7 +1125,7 @@ private JCExpression copySelect(JavacTreeMaker maker, JCFieldAccess typeParam) {
     				expression = null;
     			}
     		}
    -
    +		
     		Collections.reverse(chainNames);
     		JCExpression typeParameter = null;
     		for (Name name : chainNames) {
    @@ -1150,18 +1148,16 @@ private boolean constructorExists(JavacNode type, String builderClassName) {
     					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/HandleSuperBuilderRemove.java b/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java
    index d3db5ef4e3..483de6e190 100644
    --- a/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java
    +++ b/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java
    @@ -28,7 +28,7 @@
     import lombok.core.AlreadyHandledAnnotations;
     import lombok.core.AnnotationValues;
     import lombok.core.HandlerPriority;
    -import lombok.experimental.SuperBuilder;
    +import lombok.SuperBuilder;
     import lombok.javac.JavacAnnotationHandler;
     import lombok.javac.JavacNode;
     import lombok.spi.Provides;
    diff --git a/src/core/lombok/javac/handlers/HandleUtilityClass.java b/src/core/lombok/javac/handlers/HandleUtilityClass.java
    index b20aee3ae6..bc171b8f89 100644
    --- a/src/core/lombok/javac/handlers/HandleUtilityClass.java
    +++ b/src/core/lombok/javac/handlers/HandleUtilityClass.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2015-2021 The Project Lombok Authors.
    + * Copyright (C) 2015-2024 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
    @@ -107,7 +107,7 @@ private void changeModifiersAndGenerateConstructor(JavacNode typeNode, JavacNode
     			if ((typeDecl.mods.flags & (Flags.INTERFACE | Flags.ANNOTATION)) != 0) markStatic = false;
     		}
     		
    -		if (markStatic) classDecl.mods.flags |= Flags.STATIC;
    +		if (markStatic) markClassAsStatic(classDecl);
     		
     		for (JavacNode element : typeNode.down()) {
     			if (element.getKind() == Kind.FIELD) {
    @@ -125,21 +125,23 @@ private void changeModifiersAndGenerateConstructor(JavacNode typeNode, JavacNode
     				
     				methodDecl.mods.flags |= Flags.STATIC;
     			} else if (element.getKind() == Kind.TYPE) {
    -				JCClassDecl innerClassDecl = (JCClassDecl) element.get();
    -				innerClassDecl.mods.flags |= Flags.STATIC;
    -				ClassSymbol innerClassSymbol = innerClassDecl.sym;
    -				if (innerClassSymbol != null) {
    -					if (innerClassSymbol.type instanceof ClassType) {
    -						((ClassType) innerClassSymbol.type).setEnclosingType(Type.noType);
    -					}
    -					innerClassSymbol.erasure_field = null;
    -				}
    +				markClassAsStatic((JCClassDecl) element.get());
     			}
     		}
     		
     		if (makeConstructor) createPrivateDefaultConstructor(typeNode);
     	}
     	
    +	private void markClassAsStatic(JCClassDecl classDecl) {
    +		classDecl.mods.flags |= Flags.STATIC; // maybe Flags.NOOUTERTHIS too?
    +		ClassSymbol innerClassSymbol = classDecl.sym;
    +		if (innerClassSymbol != null && innerClassSymbol.type instanceof ClassType) {
    +			ClassType classType = (ClassType) innerClassSymbol.type;
    +			classType.setEnclosingType(Type.noType);
    +			innerClassSymbol.erasure_field = null;
    +		}
    +	}
    +	
     	private void createPrivateDefaultConstructor(JavacNode typeNode) {
     		JavacTreeMaker maker = typeNode.getTreeMaker();
     		JCModifiers mods = maker.Modifiers(Flags.PRIVATE, List.nil());
    diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java
    index 8f4b1a86a6..a3b3dfb7bd 100644
    --- a/src/core/lombok/javac/handlers/HandleVal.java
    +++ b/src/core/lombok/javac/handlers/HandleVal.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2010-2021 The Project Lombok Authors.
    + * Copyright (C) 2010-2026 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
    @@ -123,6 +123,7 @@ public void endVisitLocal(JavacNode localNode, JCVariableDecl local) {
     		if (localNode.getSourceVersion() >= 10) {
     			local.vartype = null;
     			localNode.getAst().setChanged();
    +			VarDeclDeclKind.setDeclKindToVar(local);
     			return;
     		}
     		
    @@ -211,4 +212,17 @@ private static JCTree getVarOrRecordPattern(JCEnhancedForLoop loop) {
     			return null;
     		}
     	}
    +	
    +	private static class VarDeclDeclKind {
    +		private static final Field declKind = Permit.permissiveGetField(JCVariableDecl.class, "declKind");
    +		private static final Field typePos = Permit.permissiveGetField(JCVariableDecl.class, "typePos");
    +		private static final Enum DECL_KIND_VAR = Permit.enumValue(JCVariableDecl.class.getName() + "$DeclKind", "VAR");
    +		
    +		private static void setDeclKindToVar(JCVariableDecl decl) {
    +			if (declKind != null) {
    +				Permit.permissiveSet(declKind, decl, DECL_KIND_VAR);
    +				Permit.permissiveSet(typePos, decl, decl.pos);
    +			}
    +		}
    +	}
     }
    diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java
    index c7fa053127..52f2c1928d 100644
    --- a/src/core/lombok/javac/handlers/HandleWith.java
    +++ b/src/core/lombok/javac/handlers/HandleWith.java
    @@ -229,7 +229,7 @@ public JCMethodDecl createWith(long access, JavacNode field, JavacTreeMaker make
     		
     		JCBlock methodBody = null;
     		long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, field.getContext());
    -		List annsOnParam = copyAnnotations(onParam).appendList(copyableAnnotations);
    +		List annsOnParam = copyAnnotations(onParam, maker).appendList(copyableAnnotations);
     		
     		JCExpression pType = cloneType(maker, fieldDecl.vartype, source);
     		JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, annsOnParam), fieldDecl.name, pType, null);
    @@ -278,7 +278,7 @@ public JCMethodDecl createWith(long access, JavacNode field, JavacTreeMaker make
     		List throwsClauses = List.nil();
     		JCExpression annotationMethodDefaultValue = null;
     		
    -		List annsOnMethod = copyAnnotations(onMethod);
    +		List annsOnMethod = copyAnnotations(onMethod, maker);
     		CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
     		if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil()));
     		
    diff --git a/src/core/lombok/javac/handlers/HandleWithBy.java b/src/core/lombok/javac/handlers/HandleWithBy.java
    index ff67fd9fd9..7978928fc9 100644
    --- a/src/core/lombok/javac/handlers/HandleWithBy.java
    +++ b/src/core/lombok/javac/handlers/HandleWithBy.java
    @@ -321,7 +321,7 @@ public JCMethodDecl createWithBy(long access, JavacNode field, JavacTreeMaker ma
     		List throwsClauses = List.nil();
     		JCExpression annotationMethodDefaultValue = null;
     		
    -		List annsOnMethod = copyAnnotations(onMethod);
    +		List annsOnMethod = copyAnnotations(onMethod, maker);
     		CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
     		if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil()));
     		
    diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
    index 8b5638247d..0c075295b3 100644
    --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
    +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2009-2022 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
    @@ -38,6 +38,7 @@
     import java.util.Map;
     import java.util.regex.Pattern;
     
    +import com.sun.source.tree.TreeVisitor;
     import com.sun.tools.javac.code.Attribute;
     import com.sun.tools.javac.code.BoundKind;
     import com.sun.tools.javac.code.Flags;
    @@ -75,6 +76,7 @@
     import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
     import com.sun.tools.javac.tree.JCTree.JCWildcard;
     import com.sun.tools.javac.tree.JCTree.TypeBoundKind;
    +import com.sun.tools.javac.tree.TreeCopier;
     import com.sun.tools.javac.tree.TreeMaker;
     import com.sun.tools.javac.tree.TreeScanner;
     import com.sun.tools.javac.util.Context;
    @@ -590,38 +592,42 @@ static class RecordComponentReflect {
     		
     		private static final Field astField;
     		private static final Field originalAnnos;
    -		private static final Method findRecordComponentToRemove;
    +		private static final Method getRecordComponents = Permit.permissiveGetMethod(ClassSymbol.class, "getRecordComponents");
     		
     		static {
     			Field a = null;
     			Field o = null;
    -			Method m = null;
     			try {
     				Class forName = Class.forName("com.sun.tools.javac.code.Symbol$RecordComponent");
     				a = Permit.permissiveGetField(forName, "ast");
     				o = Permit.permissiveGetField(forName, "originalAnnos");
    -				m = Permit.permissiveGetMethod(ClassSymbol.class, "findRecordComponentToRemove", JCVariableDecl.class);
     			} catch (Throwable e) {
     				// Ignore
     			}
     			astField = a;
     			originalAnnos = o;
    -			findRecordComponentToRemove = m;
     		}
     		
     		static void deleteAnnotation(JCClassDecl record, JCVariableDecl component, JCTree annotation) {
    -			if ((astField == null && originalAnnos == null) || findRecordComponentToRemove == null) return;
    +			if ((astField == null && originalAnnos == null) || getRecordComponents == null) return;
     			
     			try {
    -				Object toRemove = Permit.invokeSneaky(findRecordComponentToRemove, record.sym, component);
    -				if (astField != null) {
    -					// OpenJDK
    -					JCVariableDecl variable = Permit.get(astField, toRemove);
    -					variable.mods.annotations =  filterListByPos(variable.mods.annotations, annotation);
    -				} else {
    -					// Zulu JDK 17
    -					List annotations = Permit.get(originalAnnos, toRemove);
    -					Permit.set(originalAnnos, toRemove, filterListByPos(annotations, annotation));
    +				List recordComponents = (List) Permit.invokeSneaky(getRecordComponents, record.sym);
    +				
    +				for (Object recordComponent : recordComponents) {
    +					Name recordComponentName = ((Symbol) recordComponent).name;
    +					if (!recordComponentName.equals(component.name)) continue;
    +					
    +					if (astField != null) {
    +						// OpenJDK
    +						JCVariableDecl variable = Permit.get(astField, recordComponent);
    +						variable.mods.annotations =  filterListByPos(variable.mods.annotations, annotation);
    +					} else {
    +						// Zulu JDK 17
    +						List annotations = Permit.get(originalAnnos, recordComponent);
    +						Permit.set(originalAnnos, recordComponent, filterListByPos(annotations, annotation));
    +					}
    +					return;
     				}
     			} catch (Throwable e) {
     				// Ignore
    @@ -991,6 +997,7 @@ public static boolean isConstructorCall(final JCStatement statement) {
     	/**
     	 * Turns an {@code AccessLevel} instance into the flag bit used by javac.
     	 */
    +	@SuppressWarnings("deprecation") // We have to use MODULE here to make it act according to spec, which is to treat it like `PACKAGE`.
     	public static int toJavacModifier(AccessLevel accessLevel) {
     		switch (accessLevel) {
     		case MODULE:
    @@ -1369,6 +1376,21 @@ static void enter(ClassSymbol from, Symbol toEnter) {
     	 * Also takes care of updating the JavacAST.
     	 */
     	public static void injectMethod(JavacNode typeNode, JCMethodDecl method) {
    +		JavacNode source = typeNode.getNodeFor(getGeneratedBy(method));
    +		injectMethod(typeNode, source, method);
    +	}
    +	
    +	/**
    +	 * Adds the given new method declaration to the provided type AST Node.
    +	 * Can also inject constructors.
    +	 * 
    +	 * Also takes care of updating the JavacAST.
    +	 * 
    +	 * Ordinarily the 'source' is determined automatically. In rare cases generally involving combinations between
    +	 * annotations, some of which require re-attribution such as {@code @Delegate}, that doesn't work. This method
    +	 * exists if you explicitly have the source.
    +	 */
    +	public static void injectMethod(JavacNode typeNode, JavacNode source, JCMethodDecl method) {
     		JCClassDecl type = (JCClassDecl) typeNode.get();
     		
     		if (method.getName().contentEquals("")) {
    @@ -1389,7 +1411,7 @@ public static void injectMethod(JavacNode typeNode, JCMethodDecl method) {
     		}
     		
     		addSuppressWarningsAll(method.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(method)), typeNode.getContext());
    -		addGenerated(method.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(method)), typeNode.getContext());
    +		addGenerated(method.mods, typeNode, source, typeNode.getContext());
     		type.defs = type.defs.append(method);
     		
     		EnterReflect.memberEnter(method, typeNode);
    @@ -1527,7 +1549,7 @@ public static void addGenerated(JCModifiers mods, JavacNode node, JavacNode sour
     		if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_JAKARTA_GENERATED_ANNOTATIONS))) {
     			addAnnotation(mods, node, source, "jakarta.annotation.Generated", node.getTreeMaker().Literal("lombok"));
     		}
    -		if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) {
    +		if (!Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) {
     			addAnnotation(mods, node, source, "lombok.Generated", null);
     		}
     	}
    @@ -1731,21 +1753,41 @@ public static List findCopyableAnnotations(JavacNode node) {
     				}
     			}
     		}
    -		return copyAnnotations(result.toList());
    +
    +		return copyAnnotations(result.toList(), node.getTreeMaker());
     	}
     	
     	/**
    -	 * 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 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 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) {
    -		return findAnnotationsInList(node, COPY_TO_SETTER_ANNOTATIONS);
    +	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 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 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);
     	}
     	
     	/**
    @@ -1769,7 +1811,7 @@ private static List findAnnotationsInList(JavacNode node, java.uti
     		if (annoName == null) return List.nil();
     		
     		if (!annoName.isEmpty()) {
    -			for (String bn : annotationsToFind) if (typeMatches(bn, node, annoName)) return List.of(anno);
    +			for (String bn : annotationsToFind) if (typeMatches(bn, node, annoName)) return copyAnnotations(List.of(anno), node.getTreeMaker());
     		}
     		
     		ListBuffer result = new ListBuffer();
    @@ -1784,7 +1826,7 @@ private static List findAnnotationsInList(JavacNode node, java.uti
     				}
     			}
     		}
    -		return copyAnnotations(result.toList());
    +		return copyAnnotations(result.toList(), node.getTreeMaker());
     	}
     	
     	/**
    @@ -2093,15 +2135,21 @@ public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(Javac
     		errorNode.addError(out.append(" are not allowed on builder classes.").toString());
     	}
     	
    -	static List copyAnnotations(List in) {
    +	static List copyAnnotations(List in, JavacTreeMaker maker) {
     		ListBuffer out = new ListBuffer();
     		for (JCExpression expr : in) {
     			if (!(expr instanceof JCAnnotation)) continue;
    -			out.append((JCAnnotation) expr.clone());
    +			out.append(copyExpression((JCAnnotation) expr, maker));
     		}
     		return out.toList();
     	}
     	
    +	@SuppressWarnings("unchecked")
    +	static  T copyExpression(T expression, JavacTreeMaker maker) {
    +		TreeVisitor visitor = new TreeCopier(maker.getUnderlyingTreeMaker());
    +		return (T) expression.accept(visitor, null);
    +	}
    +	
     	static List mergeAnnotations(List a, List b) {
     		if (a == null || a.isEmpty()) return b;
     		if (b == null || b.isEmpty()) return a;
    @@ -2251,7 +2299,7 @@ private static JCExpression cloneType0(JavacTreeMaker maker, JCTree in) {
     		
     		if (JCAnnotatedTypeReflect.is(in)) {
     			JCExpression underlyingType = cloneType0(maker, JCAnnotatedTypeReflect.getUnderlyingType(in));
    -			List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in));
    +			List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in), maker);
     			return JCAnnotatedTypeReflect.create(anns, underlyingType);
     		}
     		
    diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java
    index 4954a61082..58433d6d75 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
    @@ -315,7 +315,7 @@ private void generateSingularMethod(CheckerFrameworkVersion cfv, boolean depreca
     			if (!setterPrefix.isEmpty()) name = builderType.toName(HandlerUtil.buildAccessorName(source, setterPrefix, name.toString()));
     			
     			statements.prepend(createConstructBuilderVarIfNeeded(maker, data, builderType, source));
    -			List methodAnnotations = copyAnnotations(findCopyableToBuilderSingularSetterAnnotations(data.annotation.up()));
    +			List methodAnnotations = copyAnnotations(findCopyableToBuilderSingularSetterAnnotations(data.annotation.up()), maker);
     			finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, params, methodAnnotations, access, null);
     		}
     		
    @@ -362,7 +362,7 @@ private void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate
     				statements.prepend(JavacHandlerUtil.generateNullCheck(maker, null, data.getPluralName(), builderType, "%s cannot be null"));
     			}
     
    -			List methodAnnotations = copyAnnotations(findCopyableToSetterAnnotations(data.annotation.up()));
    +			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/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java
    index e5fb440e7a..a0510f6c6b 100755
    --- a/src/delombok/lombok/delombok/Delombok.java
    +++ b/src/delombok/lombok/delombok/Delombok.java
    @@ -103,6 +103,7 @@ public void setWriter(Writer writer) {
     	private LinkedHashMap fileToBase = new LinkedHashMap();
     	private List filesToParse = new ArrayList();
     	private Map formatPrefs = new HashMap();
    +	private List preLombokProcessors = new ArrayList();
     	private List additionalAnnotationProcessors = new ArrayList();
     	
     	/** If null, output to standard out. */
    @@ -653,6 +654,10 @@ public void addFile(File base, String fileName) throws IOException {
     		fileToBase.put(f, base);
     	}
     	
    +	public void addPreLombokProcessors(AbstractProcessor processor) {
    +		preLombokProcessors.add(processor);
    +	}
    +
     	public void addAdditionalAnnotationProcessor(AbstractProcessor processor) {
     		additionalAnnotationProcessors.add(processor);
     	}
    @@ -735,6 +740,7 @@ public boolean delombok() throws IOException {
     		Map baseMap = new IdentityHashMap();
     		
     		Set processors = new LinkedHashSet();
    +		processors.addAll(preLombokProcessors);
     		processors.add(new lombok.javac.apt.LombokProcessor());
     		processors.addAll(additionalAnnotationProcessors);
     		
    diff --git a/src/delombok/lombok/delombok/DocCommentIntegrator.java b/src/delombok/lombok/delombok/DocCommentIntegrator.java
    index 945f4fa4d6..dc71fcb47d 100644
    --- a/src/delombok/lombok/delombok/DocCommentIntegrator.java
    +++ b/src/delombok/lombok/delombok/DocCommentIntegrator.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2009-2018 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
    @@ -22,23 +22,27 @@
     package lombok.delombok;
     
     import java.util.ArrayList;
    -import java.util.Iterator;
     import java.util.List;
     import java.util.Map;
    +import java.util.Map.Entry;
    +import java.util.NavigableMap;
    +import java.util.TreeMap;
     import java.util.regex.Pattern;
     
    -import lombok.javac.CommentInfo;
    -import lombok.javac.Javac;
    -import lombok.javac.PackageName;
    -import lombok.javac.handlers.JavacHandlerUtil;
    -
     import com.sun.tools.javac.parser.Tokens.Comment;
     import com.sun.tools.javac.tree.DocCommentTable;
    +import com.sun.tools.javac.tree.EndPosTable;
     import com.sun.tools.javac.tree.JCTree;
     import com.sun.tools.javac.tree.JCTree.JCClassDecl;
     import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
     import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
     import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
    +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
    +import com.sun.tools.javac.tree.TreeScanner;
    +
    +import lombok.javac.CommentInfo;
    +import lombok.javac.Javac;
    +import lombok.javac.handlers.JavacHandlerUtil;
     
     public class DocCommentIntegrator {
     	/**
    @@ -49,18 +53,21 @@ public List integrate(List comments, JCCompilationUnit
     		CommentInfo lastExcisedComment = null;
     		JCTree lastNode = null;
     		
    +		NavigableMap positionMap = buildNodePositionMap(unit);
    +		
     		for (CommentInfo cmt : comments) {
     			if (!cmt.isJavadoc()) {
     				out.add(cmt);
     				continue;
     			}
     			
    -			JCTree node = findJavadocableNodeOnOrAfter(unit, cmt.endPos);
    -			if (node == null) {
    +			Entry entry = positionMap.ceilingEntry(cmt.endPos);
    +			if (entry == null) {
     				out.add(cmt);
     				continue;
     			}
     			
    +			JCTree node = entry.getValue();
     			if (node == lastNode) {
     				out.add(lastExcisedComment);
     			}
    @@ -73,6 +80,28 @@ public List integrate(List comments, JCCompilationUnit
     		}
     		return out;
     	}
    +
    +	private NavigableMap buildNodePositionMap(JCCompilationUnit unit) {
    +		final NavigableMap positionMap = new TreeMap();
    +		unit.accept(new TreeScanner() {
    +			@Override
    +			public void visitClassDef(JCClassDecl tree) {
    +				positionMap.put(tree.pos, tree);
    +				super.visitClassDef(tree);
    +			}
    +			@Override
    +			public void visitMethodDef(JCMethodDecl tree) {
    +				positionMap.put(tree.pos, tree);
    +				super.visitMethodDef(tree);
    +			}
    +			@Override
    +			public void visitVarDef(JCVariableDecl tree) {
    +				positionMap.put(tree.pos, tree);
    +				super.visitVarDef(tree);
    +			}
    +		});
    +		return positionMap;
    +	}
     	
     	private static final Pattern CONTENT_STRIPPER = Pattern.compile("^(?:\\s*\\*)?(.*?)$", Pattern.MULTILINE);
     	@SuppressWarnings("unchecked") private boolean attach(JCCompilationUnit top, final JCTree node, CommentInfo cmt) {
    @@ -105,12 +134,45 @@ static void attach(final JCTree node, String docCommentContent, final int pos, O
     					return docCommentContent_;
     				}
     				
    +				@Override public Comment stripIndent() {
    +					return this;
    +				}
    +				
    +				@Override public DiagnosticPosition getPos() {
    +					return new DiagnosticPosition() {
    +						public JCTree getTree() {
    +							return node;
    +						}
    +						
    +						public int getStartPosition() {
    +							return pos;
    +						}
    +						
    +						public int getPreferredPosition() {
    +							return pos;
    +						}
    +						
    +						@SuppressWarnings("unused") // We compile against very old versions of javac intentionally (to support old stuff), but this is the method for newer impls.
    +						public int getEndPosition(EndPosTable endPosTable) {
    +							int end = endPosTable == null ? 0 : endPosTable.getEndPos(node);
    +							if (end > pos) return end;
    +							return pos + docCommentContent_.length();
    +						}
    +						
    +						public int getEndPosition(Map endPosTable) {
    +							Integer end = endPosTable.get(node);
    +							if (end != null && end.intValue() > pos) return end.intValue();
    +							return pos + docCommentContent_.length();
    +						}
    +					};
    +				}
    +				
     				@Override public int getSourcePos(int index) {
     					return pos + index;
     				}
     				
     				@Override public CommentStyle getStyle() {
    -					return CommentStyle.JAVADOC;
    +					return (CommentStyle) Javac.getCommentStyle();
     				}
     				
     				@Override public boolean isDeprecated() {
    @@ -119,32 +181,4 @@ static void attach(final JCTree node, String docCommentContent, final int pos, O
     			});
     		}
     	}
    -	
    -	private JCTree findJavadocableNodeOnOrAfter(JCCompilationUnit unit, int endPos) {
    -		JCTree pid = PackageName.getPackageNode(unit);
    -		if (pid != null && endPos <= pid.pos) return null;
    -		Iterator it = unit.defs.iterator();
    -		
    -		while (it.hasNext()) {
    -			JCTree node = it.next();
    -			if (node.pos < endPos) {
    -				if (node instanceof JCClassDecl) {
    -					com.sun.tools.javac.util.List defs = ((JCClassDecl) node).defs;
    -					if (!defs.isEmpty()) while (!defs.tail.isEmpty()) defs = defs.tail;
    -					if (defs.head != null && defs.head.pos >= endPos) {
    -						// The associated node is IN this class declaration, so, replace the iterator.
    -						// There's no point looking beyond this member in the current iteration 'context'
    -						// so we don't need to save the old ref. Just start over inside this type declaration.
    -						it = ((JCClassDecl) node).defs.iterator();
    -					}
    -				}
    -				continue;
    -			}
    -			
    -			if (node instanceof JCMethodDecl || node instanceof JCClassDecl || node instanceof JCVariableDecl) return node;
    -			return null;
    -		}
    -		
    -		return null;
    -	}
     }
    diff --git a/src/delombok/lombok/delombok/PrettyPrinter.java b/src/delombok/lombok/delombok/PrettyPrinter.java
    index d74344544d..f379094a9e 100644
    --- a/src/delombok/lombok/delombok/PrettyPrinter.java
    +++ b/src/delombok/lombok/delombok/PrettyPrinter.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2016-2021 The Project Lombok Authors.
    + * Copyright (C) 2016-2026 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
    @@ -92,13 +92,13 @@
     import com.sun.tools.javac.util.Position;
     
     import lombok.javac.CommentInfo;
    -import lombok.javac.Javac;
    -import lombok.javac.PackageName;
    -import lombok.permit.Permit;
     import lombok.javac.CommentInfo.EndConnection;
     import lombok.javac.CommentInfo.StartConnection;
    +import lombok.javac.Javac;
     import lombok.javac.JavacTreeMaker.TreeTag;
     import lombok.javac.JavacTreeMaker.TypeTag;
    +import lombok.javac.PackageName;
    +import lombok.permit.Permit;
     
     public class PrettyPrinter extends JCTree.Visitor {
     	private static final String LINE_SEP = System.getProperty("line.separator");
    @@ -263,6 +263,14 @@ private boolean suppress(JCTree tree) {
     		return false;
     	}
     	
    +	private void print(Name name) {
    +		if (name.isEmpty()) {
    +			print("_");
    +		} else {
    +			print(name.toString());
    +		}
    +	}
    +	
     	private void print(CharSequence s) {
     		boolean align = needsAlign;
     		if (needsNewLine && !onNewLine) println();
    @@ -506,8 +514,22 @@ private Name name_value(Name someName) {
     		println(";", tree);
     	}
     	
    +	void printModuleImport(JCTree tree) {
    +		JCExpression qualid = readObject(tree, "module", null);
    +		
    +		aPrint("import module ");
    +		print(qualid);
    +		println(";", tree);
    +	}
    +	
     	private Name currentTypeName;
     	@Override public void visitClassDef(JCClassDecl tree) {
    +		if ((tree.mods.flags & IMPLICIT_CLASS) != 0) {
    +			printClassMembers(tree.defs, false, false);
    +			consumeComments(endPos(tree));
    +			return;
    +		}
    +		
     		println();
     		printDocComment(tree);
     		align();
    @@ -689,10 +711,12 @@ private void printVarDef0(JCVariableDecl tree) {
     		 */
     		try {
     			innermostArrayBracketsAreVarargs = varargs;
    -			if (tree.vartype == null || tree.vartype.pos == -1) {
    +			if (tree.vartype == null || tree.vartype.pos == -1 || endPos(tree.vartype) == -1) {
     				print("var");
     			} else {
    -				print(tree.vartype);
    +				Object kind = readObject(tree, "declKind", null);
    +				if (kind instanceof Enum && "VAR".equals(((Enum) kind).name())) print("var");
    +				else print(tree.vartype);
     			}
     		} finally {
     			innermostArrayBracketsAreVarargs = false;
    @@ -1025,7 +1049,7 @@ private void printModifierKeywords(JCModifiers tree) {
     		if ((v & NATIVE) != 0) print("native ");
     		if ((v & ABSTRACT) != 0) print("abstract ");
     		if ((v & SEALED) != 0) print("sealed ");
    -		if ((v & NON_SEALED) != 0) print("non-sealed ");
    +		if ((v & NON_SEALED) != 0) print("non-sealed "); // Yes, this is a real keyword.
     		if ((v & STRICTFP) != 0) print("strictfp ");
     		if ((v & DEFAULT) != 0 && (v & INTERFACE) == 0) print("default ");
     	}
    @@ -1591,39 +1615,17 @@ private static String quoteChar(char ch) {
     		}
     	}
     	
    -	private static final Method getExtendsClause, getEndPosition, storeEnd;
    +	private static final Method getExtendsClause, getEndPosition;
     	
     	static {
     		getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class[0]);
     		
     		if (getJavaCompilerVersion() < 8) {
     			getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class);
    -			storeEnd = getMethod(java.util.Map.class, "put", Object.class, Object.class);
     		} else {
     			getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable");
    -			Method storeEndMethodTemp;
    -			Class endPosTable;
    -			try {
    -				endPosTable = Class.forName("com.sun.tools.javac.tree.EndPosTable");
    -			} catch (ClassNotFoundException ex) {
    -				throw sneakyThrow(ex);
    -			}
    -			try {
    -				storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class);
    -			} catch (NoSuchMethodException e) {
    -				try {
    -					endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable");
    -					storeEndMethodTemp = Permit.getMethod(endPosTable, "storeEnd", JCTree.class, int.class);
    -				} catch (NoSuchMethodException ex) {
    -					throw sneakyThrow(ex);
    -				} catch (ClassNotFoundException ex) {
    -					throw sneakyThrow(ex);
    -				}
    -			}
    -			storeEnd = storeEndMethodTemp;
     		}
     		Permit.setAccessible(getEndPosition);
    -		Permit.setAccessible(storeEnd);
     	}
     	
     	private static Method getMethod(Class clazz, String name, Class... paramTypes) {
    @@ -1722,6 +1724,10 @@ public void visitTypeBoundKind(TypeBoundKind tree) {
     			printPatternCaseLabel(tree);
     		} else if (className.endsWith("$JCRecordPattern")) { // Introduced in JDK19
     			printRecordPattern(tree);
    +		} else if (className.endsWith("$JCAnyPattern")) { // Introduced in JDK22
    +			print("_");
    +		} else if (className.endsWith("$JCModuleImport")) { // Introduced in JDK25
    +			printModuleImport(tree);
     		} else {
     			throw new AssertionError("Unhandled tree type: " + tree.getClass() + ": " + tree);
     		}
    diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
    index c176f51591..87bee957ff 100644
    --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
    +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.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
    @@ -63,6 +63,7 @@ private static void registerPatchScripts(Instrumentation instrumentation, boolea
     					if (loader.getClass().getName().startsWith("org.sonar.classloader.")) return false; // Relevant to bug #2351
     					if (loader.toString().contains("com.alexnederlof:jasperreports-plugin")) return false; //Relevant to bug #1036
     					if (loader.toString().contains("com.pro-crafting.tools:jasperreports-plugin")) return false; //Relevant to bug #1036
    +					if (loader.toString().contains("com.pro-crafting.tools:jasperreports-maven-plugin")) return false; //Relevant to bug #1036
     				}
     				if (!(loader instanceof URLClassLoader)) return true;
     				ClassLoader parent = loader.getParent();
    @@ -95,12 +96,14 @@ public String mapResourceName(int classFileFormatVersion, String resourceName) {
     		patchSyntaxAndOccurrencesHighlighting(sm);
     		patchSortMembersOperation(sm);
     		patchExtractInterfaceAndPullUp(sm);
    +		patchExtractVariable(sm);
     		patchAboutDialog(sm);
     		patchEclipseDebugPatches(sm);
     		patchJavadoc(sm);
     		patchASTConverterLiterals(sm);
     		patchASTNodeSearchUtil(sm);
     		patchFieldInitializer(sm);
    +		patchFoldingStructure(sm);
     		
     		patchPostCompileHookEcj(sm);
     		
    @@ -224,6 +227,17 @@ private static void patchExtractInterfaceAndPullUp(ScriptManager sm) {
     				.build());
     	}
     	
    +	private static void patchExtractVariable(ScriptManager sm) {
    +		/* Fix sourceEnding for generated nodes to avoid null pointer */
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.replaceMethodCall()
    +				.target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.util.SideEffectChecker", "findFunctionDefinition", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.dom.ITypeBinding", "org.eclipse.jdt.core.dom.IMethodBinding"))
    +				.methodToReplace(new Hook("org.eclipse.jdt.core.dom.NodeFinder", "perform", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.ISourceRange"))
    +				.replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "findGeneratedNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.ISourceRange", "org.eclipse.jdt.core.dom.IMethodBinding"))
    +				.requestExtra(StackRequest.PARAM2)
    +				.transplant()
    +				.build());
    +	}
    +	
     	private static void patchInline(ScriptManager sm) {
     		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.wrapReturnValue()
     				.target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.code.SourceProvider", "getCodeBlocks", "java.lang.String[]", "org.eclipse.jdt.internal.corext.refactoring.code.CallContext", "org.eclipse.jdt.core.dom.rewrite.ImportRewrite"))
    @@ -320,6 +334,13 @@ private static void patchDomAstReparseIssues(ScriptManager sm) {
     				.transplant()
     				.build());
     		
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.replaceMethodCall()
    +				.target(new MethodTarget("org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer", "visit"))
    +				.methodToReplace(new Hook("org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "getTokenEndOffset", "int", "org.eclipse.jdt.internal.compiler.parser.TerminalToken", "int"))
    +				.replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getTokenEndOffsetFixed", "int", "org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "java.lang.Object", "int", "java.lang.Object"))
    +				.requestExtra(StackRequest.PARAM1)
    +				.transplant()
    +				.build());
     	}
     	
     	private static void patchPostCompileHookEclipse(ScriptManager sm) {
    @@ -771,6 +792,13 @@ private static void addPatchesForDelegate(ScriptManager sm) {
     				.request(StackRequest.RETURN_VALUE, StackRequest.THIS)
     				.wrapMethod(new Hook("lombok.launch.PatchFixesHider$Delegate", "addGeneratedDelegateMethods", "java.lang.Object[]", "java.lang.Object", "java.lang.Object"))
     				.build());
    +		
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.exitEarly()
    +				.target(new MethodTarget("org.eclipse.jdt.internal.core.JavaElement", "getElementInfo", "org.eclipse.jdt.internal.compiler.env.IElementInfo"))
    +				.request(StackRequest.THIS)
    +				.decisionMethod(new Hook("lombok.launch.PatchFixesHider$Delegate", "isDelegateSourceMethod", "boolean", "java.lang.Object"))
    +				.valueMethod(new Hook("lombok.launch.PatchFixesHider$Delegate", "returnElementInfo", "java.lang.Object", "java.lang.Object"))
    +				.build());
     	}
     	
     	private static void addPatchesForValEclipse(ScriptManager sm) {
    @@ -867,6 +895,14 @@ private static void patchFixSourceTypeConverter(ScriptManager sm) {
     				.target(new MethodTarget(SOURCE_TYPE_CONVERTER_SIG, "convertAnnotations", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG))
     				.wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG))
     				.request(StackRequest.PARAM1, StackRequest.RETURN_VALUE).build());
    +		
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.exitEarly()
    +				.target(new MethodTarget(SOURCE_TYPE_CONVERTER_SIG, "parseMemberValue", "org.eclipse.jdt.internal.compiler.ast.Expression", "char[]"))
    +				.decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isEmpty", "boolean", "char[]"))
    +				.valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnNullExpression", "org.eclipse.jdt.internal.compiler.ast.Expression", "java.lang.Object"))
    +				.request(StackRequest.PARAM1)
    +				.transplant()
    +				.build());
     	}
     	
     	private static void patchEclipseDebugPatches(ScriptManager sm) {
    @@ -967,6 +1003,14 @@ private static void patchNullCheck(ScriptManager sm) {
     	}
     	
     	private static void patchJavadoc(ScriptManager sm) {
    +		// Moved to new package and changed to instance method in 2024-03
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.wrapMethodCall()
    +				.target(new MethodTarget("org.eclipse.jdt.core.manipulation.internal.javadoc.CoreJavadocAccess", "getHTMLContent", "java.lang.String", "org.eclipse.jdt.core.IJavaElement", "boolean"))
    +				.methodToWrap(new Hook("org.eclipse.jdt.core.manipulation.internal.javadoc.CoreJavadocAccess", "getHTMLContentFromSource", "java.lang.String", "org.eclipse.jdt.core.IJavaElement"))
    +				.wrapMethod(new Hook("lombok.launch.PatchFixesHider$Javadoc", "getHTMLContentFromSource", "java.lang.String", "java.lang.String", "java.lang.Object", "org.eclipse.jdt.core.IJavaElement"))
    +				.requestExtra(StackRequest.THIS, StackRequest.PARAM1)
    +				.build());
    +		
     		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.wrapMethodCall()
     				.target(new MethodTarget("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContent", "java.lang.String", "org.eclipse.jdt.core.IJavaElement", "boolean"))
     				.methodToWrap(new Hook("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContentFromSource", "java.lang.String", "org.eclipse.jdt.core.IJavaElement"))
    @@ -1030,6 +1074,30 @@ private static void patchASTNodeSearchUtil(ScriptManager sm) {
     				.build());
     	}
     	
    +	private static void patchFoldingStructure(ScriptManager sm) {
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.exitEarly()
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "createFoldingRegionForTryBlock", "void", "org.eclipse.jdt.core.dom.TryStatement"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "createFoldingRegionForFinallyBlock", "void", "org.eclipse.jdt.core.dom.TryStatement"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "createFoldingRegionForStatement", "void", "org.eclipse.jdt.core.dom.ASTNode"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "createFoldingRegion", "void", "org.eclipse.jdt.core.dom.ASTNode", "boolean"))
    +			.decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode"))
    +			.request(StackRequest.PARAM1)
    +			.transplant()
    +			.build());
    +		
    +		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.exitEarly()
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.TypeDeclaration"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.MethodDeclaration"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.IfStatement"))
    +			.target(new MethodTarget("org.eclipse.jdt.ui.text.folding.DefaultJavaFoldingStructureProvider$FoldingVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.IfStatement"))
    +			.decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode"))
    +			.request(StackRequest.PARAM1)
    +			.valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnFalse", "boolean", "java.lang.Object"))
    +			.transplant()
    +			.build());
    +		
    +	}
    +	
     	private static void patchFieldInitializer(ScriptManager sm) {
     		sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.addField()
     			.targetClass("org.eclipse.jdt.internal.core.CompilationUnitStructureRequestor")
    @@ -1113,6 +1181,23 @@ private static void patchForTests(ScriptManager sm) {
     				.request(StackRequest.THIS, StackRequest.PARAM2)
     				.transplant()
     				.build());
    +		
    +		// Remove implicit record fields in tests
    +		sm.addScriptIfWitness(ECLIPSE_TEST_CLASSES, ScriptBuilder.exitEarly()
    +				.target(new MethodTarget("org.eclipse.jdt.internal.compiler.ast.FieldDeclaration", "print", "java.lang.StringBuilder", "int", "java.lang.StringBuilder"))
    +				.decisionMethod(new Hook("lombok.launch.PatchFixesHider$Tests", "isRecordComponent", "boolean", "org.eclipse.jdt.internal.compiler.ast.FieldDeclaration", "java.lang.Object"))
    +				.valueMethod(new Hook("lombok.launch.PatchFixesHider$Tests", "returnStringBuilder", "java.lang.StringBuilder", "java.lang.Object", "java.lang.StringBuilder"))
    +				.request(StackRequest.THIS, StackRequest.PARAM2)
    +				.transplant()
    +				.build());
    +		
    +		sm.addScriptIfWitness(ECLIPSE_TEST_CLASSES, ScriptBuilder.exitEarly()
    +				.target(new MethodTarget("org.eclipse.jdt.internal.compiler.ast.FieldDeclaration", "print", "java.lang.StringBuffer", "int", "java.lang.StringBuffer"))
    +				.decisionMethod(new Hook("lombok.launch.PatchFixesHider$Tests", "isRecordComponent", "boolean", "org.eclipse.jdt.internal.compiler.ast.FieldDeclaration", "java.lang.Object"))
    +				.valueMethod(new Hook("lombok.launch.PatchFixesHider$Tests", "returnStringBuffer", "java.lang.StringBuffer", "java.lang.Object", "java.lang.StringBuffer"))
    +				.request(StackRequest.THIS, StackRequest.PARAM2)
    +				.transplant()
    +				.build());
     	}
     
     }
    diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
    index d7b1759839..547810e90f 100644
    --- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
    +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2010-2021 The Project Lombok Authors.
    + * Copyright (C) 2010-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
    @@ -21,10 +21,11 @@
      */
     package lombok.eclipse.agent;
     
    -import static lombok.eclipse.Eclipse.*;
     import static lombok.eclipse.EcjAugments.*;
    +import static lombok.eclipse.Eclipse.*;
     import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
     
    +import java.lang.reflect.Field;
     import java.lang.reflect.Method;
     import java.util.ArrayList;
     import java.util.Arrays;
    @@ -47,13 +48,13 @@
     import org.eclipse.jdt.internal.compiler.CompilationResult;
     import org.eclipse.jdt.internal.compiler.ast.ASTNode;
     import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
    +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
     import org.eclipse.jdt.internal.compiler.ast.Annotation;
     import org.eclipse.jdt.internal.compiler.ast.Argument;
     import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
     import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
     import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
     import org.eclipse.jdt.internal.compiler.ast.Expression;
    -import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
     import org.eclipse.jdt.internal.compiler.ast.FieldReference;
     import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
     import org.eclipse.jdt.internal.compiler.ast.MessageSend;
    @@ -128,7 +129,8 @@ private static String nameOfScope(ClassScope scope) {
     	}
     	
     	private static boolean hasDelegateMarkedFieldsOrMethods(TypeDeclaration decl) {
    -		if (decl.fields != null) for (FieldDeclaration field : decl.fields) {
    +		AbstractVariableDeclaration[] fields = getFieldsOrRecordComponents(decl);
    +		if (fields != null) for (AbstractVariableDeclaration field : fields) {
     			if (field.annotations == null) continue;
     			for (Annotation ann : field.annotations) {
     				if (isDelegate(ann, decl)) return true;
    @@ -223,11 +225,19 @@ public static void markHandled(Annotation annotation) {
     		Annotation_applied.set(annotation, true);
     	}
     	
    +	private static AbstractVariableDeclaration[] getFieldsOrRecordComponents(TypeDeclaration decl) {
    +		if (isRecord(decl)) return getRecordComponents(decl);
    +		return decl.fields;
    +	}
    +	
     	private static void fillMethodBindingsForFields(CompilationUnitDeclaration cud, ClassScope scope, List methodsToDelegate) {
     		TypeDeclaration decl = scope.referenceContext;
     		if (decl == null) return;
     		
    -		if (decl.fields != null) for (FieldDeclaration field : decl.fields) {
    +		AbstractVariableDeclaration[] fields = getFieldsOrRecordComponents(decl);
    +		if (fields == null) return;
    +		
    +		for (AbstractVariableDeclaration field : fields) {
     			if (field.annotations == null) continue;
     			for (Annotation ann : field.annotations) {
     				if (!isDelegate(ann, decl)) continue;
    @@ -726,6 +736,15 @@ public static Object[] addGeneratedDelegateMethods(Object[] returnValue, Object
     		return EclipseOnlyMethods.addGeneratedDelegateMethodsToChildren(returnValue, javaElement);
     	}
     	
    +	public static Object returnElementInfo(Object delegateSourceMethod) {
    +		Field field = Permit.permissiveGetField(delegateSourceMethod.getClass(), "sourceMethodInfo");
    +		return Permit.permissiveReadField(Object.class, field, delegateSourceMethod);
    +	}
    +	
    +	public static boolean isDelegateSourceMethod(Object sourceMethod) {
    +		return sourceMethod.getClass().getName().equals("lombok.eclipse.agent.PatchDelegate$EclipseOnlyMethods$DelegateSourceMethod");
    +	}
    +	
     	public static class EclipseOnlyMethods {
     		private static void cleanupDelegateMethods(CompilationUnitDeclaration cud) {
     			CompilationUnit compilationUnit = getCompilationUnit(cud);
    diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
    index 3b89c0af02..555c84b0c2 100644
    --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
    +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2012-2023 The Project Lombok Authors.
    + * Copyright (C) 2012-2024 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
    @@ -321,9 +321,11 @@ public static TypeBinding resolveType(TypeBinding resolvedType, MessageSend meth
     				MethodBinding fixedBinding = scope.getMethod(extensionMethod.declaringClass, methodCall.selector, argumentTypes.toArray(new TypeBinding[0]), methodCall);
     				if (fixedBinding instanceof ProblemMethodBinding) {
     					methodCall.arguments = originalArgs;
    -					if (fixedBinding.declaringClass != null) {
    -						PostponedInvalidMethodError.invoke(scope.problemReporter(), methodCall, fixedBinding, scope);
    +					// Sometimes the declaring class is null, in that case we have to create a new ProblemMethodBinding using the extension method's declaring class
    +					if (fixedBinding.declaringClass == null) {
    +						fixedBinding = new ProblemMethodBinding(fixedBinding.selector, fixedBinding.parameters, extensionMethod.declaringClass, fixedBinding.problemId());
     					}
    +					PostponedInvalidMethodError.invoke(scope.problemReporter(), methodCall, fixedBinding, scope);
     				} else {
     					// If the extension method uses varargs, the last fixed binding parameter is an array but 
     					// the method arguments are not. Even thought we already know that the method is fine we still
    diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java b/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java
    index f270e19ff2..2d39f8b4f9 100644
    --- a/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.java
    +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchJavadoc.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
    @@ -24,25 +24,20 @@
     import static lombok.eclipse.EcjAugments.CompilationUnit_javadoc;
     
     import java.lang.reflect.Method;
    -import java.lang.reflect.InvocationTargetException;
     import java.util.Map;
     
     import org.eclipse.jdt.core.ICompilationUnit;
     import org.eclipse.jdt.core.IJavaElement;
     import org.eclipse.jdt.core.IMember;
    -import org.eclipse.jdt.internal.compiler.ast.ASTNode;
    -import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
    -import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
     import org.eclipse.jdt.internal.core.CompilationUnit;
     import org.eclipse.jdt.internal.core.SourceMethod;
     import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2;
     
    -import lombok.eclipse.handlers.EclipseHandlerUtil;
     import lombok.permit.Permit;
     
     public class PatchJavadoc {
     	
    -	public static String getHTMLContentFromSource(String original, Object member) {
    +	public static String getHTMLContentFromSource(Object instance, String original, Object member) {
     		if (original != null) {
     			return original;
     		}
    @@ -59,7 +54,7 @@ public static String getHTMLContentFromSource(String original, Object member) {
     				String rawJavadoc = docs.get(signature);
     				if (rawJavadoc == null) return null;
     				
    -				return Reflection.javadoc2HTML((IMember) member, (IJavaElement) member, rawJavadoc);
    +				return Reflection.javadoc2HTML(instance, (IMember) member, (IJavaElement) member, rawJavadoc);
     			}
     		}
     		
    @@ -83,16 +78,17 @@ static final String getSignature(SourceMethod sourceMethod) {
     	}
     	
     	/**
    -	 * 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 {
     		private static final Method javadoc2HTML;
     		private static final Method oldJavadoc2HTML;
    +		private static final Method reallyOldJavadoc2HTML;
     		private static final Method lsJavadoc2HTML;
     		static {
    -			Method a = null, b = null, c = null;
    +			Method a = null, b = null, c = null, d = null;
     			
     			try {
     				a = Permit.getMethod(JavadocContentAccess2.class, "javadoc2HTML", IMember.class, IJavaElement.class, String.class);
    @@ -103,30 +99,41 @@ private static class Reflection {
     			try {
     				c = Permit.getMethod(Class.forName("org.eclipse.jdt.ls.core.internal.javadoc.JavadocContentAccess2"), "javadoc2HTML", IMember.class, IJavaElement.class, String.class);
     			} catch (Throwable t) {}
    +			try {
    +				d = Permit.getMethod(Class.forName("org.eclipse.jdt.core.manipulation.internal.javadoc.CoreJavadocAccess"), "javadoc2HTML", IMember.class, IJavaElement.class, String.class);
    +			} catch (Throwable t) {}
     			
    -			javadoc2HTML = a;
    -			oldJavadoc2HTML = b;
    +			oldJavadoc2HTML = a;
    +			reallyOldJavadoc2HTML = b;
     			lsJavadoc2HTML = c;
    +			javadoc2HTML = d;
     		}
     		
    -		private static String javadoc2HTML(IMember member, IJavaElement element, String rawJavadoc) {
    +		private static String javadoc2HTML(Object instance, IMember member, IJavaElement element, String rawJavadoc) {
     			if (javadoc2HTML != null) {
     				try {
    -					return (String) javadoc2HTML.invoke(null, member, element, rawJavadoc);
    +					return (String) javadoc2HTML.invoke(instance, member, element, rawJavadoc);
    +				} catch (Throwable t) {
    +					return null;
    +				}
    +			}
    +			if (oldJavadoc2HTML != null) {
    +				try {
    +					return (String) oldJavadoc2HTML.invoke(instance, member, element, rawJavadoc);
     				} catch (Throwable t) {
     					return null;
     				}
     			}
     			if (lsJavadoc2HTML != null) {
     				try {
    -					return (String) lsJavadoc2HTML.invoke(null, member, element, rawJavadoc);
    +					return (String) lsJavadoc2HTML.invoke(instance, member, element, rawJavadoc);
     				} catch (Throwable t) {
     					return null;
     				}
     			}
    -			if (oldJavadoc2HTML != null) {
    +			if (reallyOldJavadoc2HTML != null) {
     				try {
    -					return (String) oldJavadoc2HTML.invoke(null, member, rawJavadoc);
    +					return (String) reallyOldJavadoc2HTML.invoke(instance, member, rawJavadoc);
     				} catch (Throwable t) {
     					return null;
     				}
    diff --git a/src/eclipseAgent/lombok/launch/PatchFixesHider.java b/src/eclipseAgent/lombok/launch/PatchFixesHider.java
    index 3bdf7974f1..40af3db23b 100755
    --- a/src/eclipseAgent/lombok/launch/PatchFixesHider.java
    +++ b/src/eclipseAgent/lombok/launch/PatchFixesHider.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2010-2024 The Project Lombok Authors.
    + * Copyright (C) 2010-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
    @@ -41,12 +41,16 @@
     import org.eclipse.jdt.core.IAnnotation;
     import org.eclipse.jdt.core.IJavaElement;
     import org.eclipse.jdt.core.IMethod;
    +import org.eclipse.jdt.core.ISourceRange;
     import org.eclipse.jdt.core.IType;
     import org.eclipse.jdt.core.JavaModelException;
     import org.eclipse.jdt.core.dom.AST;
     import org.eclipse.jdt.core.dom.ASTNode;
    +import org.eclipse.jdt.core.dom.CompilationUnit;
    +import org.eclipse.jdt.core.dom.IMethodBinding;
     import org.eclipse.jdt.core.dom.MethodDeclaration;
     import org.eclipse.jdt.core.dom.Name;
    +import org.eclipse.jdt.core.dom.NodeFinder;
     import org.eclipse.jdt.core.dom.ReturnStatement;
     import org.eclipse.jdt.core.dom.SimpleName;
     import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
    @@ -277,11 +281,16 @@ public static void transform_swapped(Object ast, Object parser) throws IOExcepti
     	public static final class Delegate {
     		private static final Method HANDLE_DELEGATE_FOR_TYPE;
     		private static final Method ADD_GENERATED_DELEGATE_METHODS;
    +		public static final Method IS_DELEGATE_SOURCE_METHOD;
    +		public static final Method RETURN_ELEMENT_INFO;
     		
     		static {
    -			Class shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchDelegatePortal");
    -			HANDLE_DELEGATE_FOR_TYPE = Util.findMethod(shadowed, "handleDelegateForType", Object.class);
    -			ADD_GENERATED_DELEGATE_METHODS = Util.findMethod(shadowed, "addGeneratedDelegateMethods", Object.class, Object.class);
    +			Class shadowedPortal = Util.shadowLoadClass("lombok.eclipse.agent.PatchDelegatePortal");
    +			HANDLE_DELEGATE_FOR_TYPE = Util.findMethod(shadowedPortal, "handleDelegateForType", Object.class);
    +			ADD_GENERATED_DELEGATE_METHODS = Util.findMethod(shadowedPortal, "addGeneratedDelegateMethods", Object.class, Object.class);
    +			Class shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchDelegate");
    +			IS_DELEGATE_SOURCE_METHOD = Util.findMethod(shadowed, "isDelegateSourceMethod", Object.class);
    +			RETURN_ELEMENT_INFO = Util.findMethod(shadowed, "returnElementInfo", Object.class);
     		}
     		
     		public static boolean handleDelegateForType(Object classScope) {
    @@ -291,6 +300,14 @@ public static boolean handleDelegateForType(Object classScope) {
     		public static Object[] addGeneratedDelegateMethods(Object returnValue, Object javaElement) {
     			return (Object[]) Util.invokeMethod(ADD_GENERATED_DELEGATE_METHODS, returnValue, javaElement);
     		}
    +		
    +		public static boolean isDelegateSourceMethod(Object sourceMethod) {
    +			return (Boolean) Util.invokeMethod(IS_DELEGATE_SOURCE_METHOD, sourceMethod);
    +		}
    +		
    +		public static Object returnElementInfo(Object delegateSourceMethod) {
    +			return Util.invokeMethod(RETURN_ELEMENT_INFO, delegateSourceMethod);
    +		}
     	}
     	
     	/** Contains patch code to support {@code val} (eclipse specific) */
    @@ -432,25 +449,18 @@ public static Object modifyMethodPattern(Object original) {
     	/** Contains patch code to support Javadoc for generated methods */
     	public static final class Javadoc {
     		private static final Method GET_HTML;
    -		private static final Method PRINT_METHOD_OLD;
    -		private static final Method PRINT_METHOD_NEW;
     		
     		static {
     			Class shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchJavadoc");
    -			GET_HTML = Util.findMethod(shadowed, "getHTMLContentFromSource", String.class, Object.class);
    -			PRINT_METHOD_NEW = Util.findMethod(shadowed, "printMethod", AbstractMethodDeclaration.class, Integer.class, StringBuilder.class, TypeDeclaration.class);
    -			PRINT_METHOD_OLD = Util.findMethod(shadowed, "printMethod", AbstractMethodDeclaration.class, Integer.class, StringBuffer.class, TypeDeclaration.class);
    +			GET_HTML = Util.findMethod(shadowed, "getHTMLContentFromSource", Object.class, String.class, Object.class);
     		}
     		
     		public static String getHTMLContentFromSource(String original, IJavaElement member) {
    -			return (String) Util.invokeMethod(GET_HTML, original, member);
    +			return (String) Util.invokeMethod(GET_HTML, null, original, member);
     		}
     		
    -		public static StringBuilder printMethod(AbstractMethodDeclaration methodDeclaration, int tab, StringBuilder output, TypeDeclaration type) {
    -				return (StringBuilder) Util.invokeMethod(PRINT_METHOD_NEW, methodDeclaration, tab, output, type);
    -		}
    -		public static StringBuffer printMethod(AbstractMethodDeclaration methodDeclaration, int tab, StringBuffer output, TypeDeclaration type) {
    -			return (StringBuffer) Util.invokeMethod(PRINT_METHOD_OLD, methodDeclaration, tab, output, type);
    +		public static String getHTMLContentFromSource(String original, Object instance, IJavaElement member) {
    +			return (String) Util.invokeMethod(GET_HTML, instance, original, member);
     		}
     	}
     	
    @@ -473,7 +483,7 @@ public static boolean isGenerated(org.eclipse.jdt.core.dom.ASTNode node) {
     			}
     			return result;
     		}
    -
    +		
     		public static boolean isGenerated(org.eclipse.jdt.internal.compiler.ast.ASTNode node) {
     			boolean result = false;
     			try {
    @@ -483,7 +493,7 @@ public static boolean isGenerated(org.eclipse.jdt.internal.compiler.ast.ASTNode
     			}
     			return result;
     		}
    -
    +		
     		public static boolean isGenerated(org.eclipse.jdt.core.IMember member) {
     			boolean result = false;
     			try {
    @@ -773,6 +783,7 @@ public static RewriteEvent[] listRewriteHandleGeneratedMethods(RewriteEvent pare
     			return newChildren.toArray(new RewriteEvent[0]);
     		}
     		
    +		// Eclipse changed a method. Older versions have `getTokenOffset(int, int)` whereas newer ones have `getTokenOffset(TerminalToken, int)`. See https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3303
     		public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int startOffset, Object domNode) throws CoreException {
     			boolean isGenerated = false;
     			try {
    @@ -781,7 +792,37 @@ public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int st
     				// If this fails, better to break some refactor scripts than to crash eclipse.
     			}
     			if (isGenerated) return -1;
    -			return scanner.getTokenEndOffset(token, startOffset);
    +			
    +			// `scanner.getTokenEndOffset(int, int)` is public so we can just call it directly, except it might not exist (in more recent versions of eclipse).
    +			// Ordinarily that means you just write a private static class and shove the code in there to avoid classloader errors, but we're in PatchFixes and we need to be careful about that sort of thing.
    +			try {
    +				Method m = Permit.getMethod(TokenScanner.class, "getTokenEndOffset", int.class, int.class);
    +				return (Integer) Permit.invoke(m, scanner, token, startOffset);
    +			} catch (Exception e) {
    +				// This is bizarre; we replaced a call to this exact method which strongly suggests it should be there. It's not going to be a nice experience in eclipse to 'break' token offsets,
    +				// but breaking token offsets is less dire than just hard crashing the entire editor with an exception.
    +				return -1;
    +			}
    +		}
    +		
    +		public static int getTokenEndOffsetFixed(TokenScanner scanner, Object token, int startOffset, Object domNode) throws CoreException {
    +			boolean isGenerated = false;
    +			try {
    +				isGenerated = (Boolean) domNode.getClass().getField("$isGenerated").get(domNode);
    +			} catch (Exception e) {
    +				// If this fails, better to break some refactor scripts than to crash eclipse.
    +			}
    +			if (isGenerated) return -1;
    +			
    +			try {
    +				Class terminalTokenClass = Class.forName("org.eclipse.jdt.internal.compiler.parser.TerminalToken");
    +				Method m = Permit.getMethod(TokenScanner.class, "getTokenEndOffset", terminalTokenClass, int.class);
    +				return (Integer) Permit.invoke(m, scanner, token, startOffset);
    +			} catch (Exception e) {
    +				// This is bizarre; we replaced a call to this exact method which strongly suggests it should be there. It's not going to be a nice experience in eclipse to 'break' token offsets,
    +				// but breaking token offsets is less dire than just hard crashing the entire editor with an exception.
    +				return -1;
    +			}
     		}
     		
     		public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception {
    @@ -886,6 +927,14 @@ public static Annotation[] convertAnnotations(Annotation[] out, IAnnotatable ann
     			return replace;
     		}
     		
    +		public static boolean isEmpty(char[] array) {
    +			return array.length == 0;
    +		}
    +		
    +		public static Expression returnNullExpression(Object arg0) {
    +			return null;
    +		}
    +		
     		public static String getRealNodeSource(String original, org.eclipse.jdt.internal.compiler.ast.ASTNode node) {
     			if (!isGenerated(node)) return original;
     			
    @@ -937,6 +986,16 @@ public static String[] getRealCodeBlocks(String[] blocks, SourceProvider sourceP
     				return blocks;
     			}
     		}
    +		
    +		public static ASTNode findGeneratedNode(ASTNode root, ISourceRange sourceRange, IMethodBinding methodBinding) {
    +			ASTNode result = NodeFinder.perform(root, sourceRange);
    +			if (result instanceof MethodDeclaration){
    +				return result;
    +			}
    +			
    +			CompilationUnit cu = (CompilationUnit) root;
    +			return cu.findDeclaringNode(methodBinding.getKey());
    +		}
     	}
     	
     	public static class FieldInitializer {
    @@ -1058,6 +1117,10 @@ public static boolean isImplicitCanonicalConstructor(AbstractMethodDeclaration m
     			return (method.bits & IsCanonicalConstructor) != 0 && (method.bits & IsImplicit) != 0;
     		}
     		
    +		public static boolean isRecordComponent(FieldDeclaration field, Object parameter) {
    +			return (field.modifiers & AccRecord) != 0;
    +		}
    +		
     		public static StringBuffer returnStringBuffer(Object p1, StringBuffer buffer) {
     			return buffer;
     		}
    diff --git a/src/installer/lombok/installer/Installer.java b/src/installer/lombok/installer/Installer.java
    index 1ca7706518..61cb618d2a 100644
    --- a/src/installer/lombok/installer/Installer.java
    +++ b/src/installer/lombok/installer/Installer.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (C) 2009-2021 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
    @@ -92,6 +92,16 @@ static void autoDiscover(List locations, List executableNames(String baseName) {
    -		String base = baseName.toLowerCase();
    -		return Collections.unmodifiableList(Arrays.asList(base, base + ".app", base + ".exe", base + "c.exe"));
    +		String[] bases = new String[] {baseName, baseName.toLowerCase()};
    +		String[] suffixes = new String[] {"", ".app", ".exe", "c.exe"};
    +		
    +		List result = new ArrayList();
    +		
    +		for (String base : bases) {
    +			for (String suffix : suffixes) {
    +				result.add(base + suffix);
    +			}
    +		}
    +		return Collections.unmodifiableList(result);
     	}
     	
     	private static List generateAlternatives(String[] roots, String pathSeparator, Collection alternatives) {
    diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
    index 5b8e3211da..e64546f4ca 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
    @@ -43,7 +43,6 @@
     import java.util.Map;
     import java.util.Map.Entry;
     import java.util.Set;
    -import java.util.Vector;
     import java.util.WeakHashMap;
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.ConcurrentMap;
    @@ -67,14 +66,14 @@
      * 

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