diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..be6d63e5d2
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: mapstruct
+open_collective: mapstruct
diff --git a/.github/INCIDENT_RESPONSE.md b/.github/INCIDENT_RESPONSE.md
new file mode 100644
index 0000000000..139ee6cdb3
--- /dev/null
+++ b/.github/INCIDENT_RESPONSE.md
@@ -0,0 +1,134 @@
+# MapStruct Incident Response Plan
+
+This document is the playbook MapStruct maintainers follow when an incident is active. It is not a replacement for ordinary bug triage — regular contributions and bug reports still flow through the process described in [`CONTRIBUTING.md`](../CONTRIBUTING.md). For instructions on how to *report* a security vulnerability, see [`SECURITY.md`](../SECURITY.md).
+
+MapStruct is a compile-time Java annotation processor distributed via Maven Central. The incidents this plan covers reflect that reality: immutable published artifacts, a manual release workflow, and a small maintainer team. There is no running service to page someone about at 3am.
+
+## What counts as an incident
+
+Four categories, with a concrete MapStruct-flavored example of each:
+
+* **Security vulnerability / CVE** — e.g. a crafted `@Mapper` input triggers arbitrary code execution in the annotation processor during `javac`, or the generated code contains an injection vulnerability.
+* **Critical bug breaking user builds** — e.g. a released `1.x.y` generates non-compiling code for all users of `@Mapping`, with no workaround short of pinning to the previous version.
+* **Release / supply-chain issue** — e.g. a bad artifact reaches Maven Central, a release secret is leaked, or `release.yml` fails mid-publish after already pushing version-bump commits to `main`.
+* **Infra / CI incident** — e.g. a GitHub Actions workflow blocks all PRs, CodeQL flags a real finding, or the mapstruct.org website is out of sync after a release.
+
+Ordinary user bugs filed as issues are *not* incidents — they follow the normal contribution flow.
+
+## Severity levels
+
+| Severity | Definition | MapStruct example |
+|---|---|---|
+| **SEV-0** | Active exploitation or supply-chain compromise of a published artifact; users must stop using a version immediately. | Compromised signed JAR on Maven Central; leaked GPG or Sonatype credentials with evidence of misuse; RCE in the annotation processor triggered during `javac`. |
+| **SEV-1** | Released version is broken for a majority of users or contains an undisclosed security flaw with no workaround. | A `1.x.y` release generates non-compiling code for all users of `@Mapping`; confirmed CVE with CVSS ≥ 7 but not yet exploited. |
+| **SEV-2** | Released version broken for a specific but significant cohort; workaround exists; or lower-severity CVE; or the release pipeline is blocked. | Regression affecting only Java 21 + records; CVE with CVSS < 7; JReleaser fails staging so a release is blocked but nothing published; CI red on `main`. |
+| **SEV-3** | Degraded but not blocking. | CodeQL false-positive noise; a single OS matrix leg flaky; mapstruct.org docs out of sync post-release. |
+
+## General response flow
+
+Every incident follows the same six-step spine, regardless of category:
+
+1. **Acknowledge** — confirm the report has been received and is being looked at.
+2. **Assess severity** — assign SEV-0 / SEV-1 / SEV-2 / SEV-3 using the table above.
+3. **Declare** — open the tracking artifact (GitHub Security Advisory for security issues, pinned issue for regressions, Discussion thread for CI incidents).
+4. **Mitigate** — give users a workaround, yank a bad release, or disable a broken workflow — whatever stops the immediate bleeding.
+5. **Fix and verify** — land the real fix, add a regression test, cut a patch release if needed, verify the fix on the published artifact.
+6. **Close out** — announce resolution on the public channels, credit reporters, update this document if the process needs changing.
+
+The playbooks below are specializations of this spine.
+
+## Playbooks
+
+### A. Security vulnerability / CVE
+
+1. Acknowledge receipt within 48 hours via GitHub Private Vulnerability Reporting.
+2. Confirm reproducibility against the latest release and `main`; assign a CVSS score.
+3. Open a **draft GitHub Security Advisory (GHSA)** — this is the tracking artifact. Do **not** open a public issue.
+4. Request a CVE via the GHSA (GitHub auto-requests from MITRE).
+5. Develop the fix on the private fork the advisory creates; maintainers review before merge.
+6. Merge the private-fork fix into `main` using the "Merge" button on the advisory page; confirm CI is green on `main` before proceeding.
+7. Coordinate a disclosure date with the reporter — target ≤ 90 days from report, sooner for SEV-0.
+8. Add a `### Security` entry to `NEXT_RELEASE_CHANGELOG.md`, then cut a patch release from `main`; verify the GPG signature and checksums on the published artifact before publishing the advisory.
+9. Publish the GHSA, announce on Discussions + mapstruct.org blog, and credit the reporter unless they decline.
+
+### B. Critical bug breaking user builds
+
+1. Reproduce the bug against the reported version using a minimal `@Mapper` (ideally in `integrationtest/`).
+2. Use `git bisect` across tagged releases to find the introducing commit.
+3. Open a pinned GitHub issue titled `[REGRESSION ] ...`, labeled `regression` and `priority:critical`.
+4. Post a user-facing workaround (e.g. pin `` to the prior release) in the issue within 24 hours.
+5. Decide between a patch release or a revert-only fix. For SEV-1, target a patch release within 72 hours.
+6. Add a regression test in `processor/src/test/` that locks the behavior.
+7. Update `NEXT_RELEASE_CHANGELOG.md` under `### Bugs`, run the `release.yml` workflow dispatch, verify the artifact lands on Central before closing the issue.
+8. Announce on Discussions; link from the pinned issue.
+
+### C. Release / supply-chain issue
+
+1. **Stop the bleeding** — if `release.yml` is still running, cancel the run from the Actions UI immediately.
+2. Assess blast radius: did artifacts reach Maven Central staging only, Central itself, or also the mapstruct.org website?
+3. If any secret is suspected compromised, rotate **before** taking any other recovery action: `GPG_PASSPHRASE`, `GPG_PUBLIC_KEY`, `GPG_PRIVATE_KEY`, `SONATYPE_USERNAME`, `SONATYPE_PASSWORD`, `SONATYPE_CENTRAL_USERNAME`, `SONATYPE_CENTRAL_TOKEN`, `GIT_WEBSITE_ACCESS_TOKEN`. Revoke the GPG key via a public keyserver. Rotate in this order: **Sonatype first** (stops further publishes), **GPG second** (revoke publicly), **website PAT last** — see the rotation order guidance in the **Secrets and rotation** section below.
+4. If a bad artifact reached Maven Central: **you cannot delete it.** Publish a superseding version immediately (e.g. `1.6.4` to replace a bad `1.6.3`), and mark the bad version yanked via the **Yanked-release procedure** section below.
+5. If `main` was corrupted (stray version bump, bad tag): revert commits, recreate the tag, coordinate any force-push with maintainers' explicit agreement.
+6. If the website push succeeded but the release failed: manually revert the corresponding commit on the `mapstruct/mapstruct.org` repo.
+7. Open a SEV-0 or SEV-1 tracking issue; freeze further releases until the root cause is known.
+8. Replay the release from a clean state, verifying signatures at each step, then announce the yank on Discussions and mapstruct.org.
+
+### D. Infra / CI incident
+
+1. Identify scope: single workflow (e.g. `windows.yml`), all workflows, or a GitHub Actions-wide outage.
+2. Check [githubstatus.com](https://www.githubstatus.com) — if the cause is upstream, park and monitor.
+3. If it is our bug: open an issue labeled `ci`, and disable the affected workflow (comment the `on:` block or gate with `if: false`) to unblock contributors.
+4. If contributor PRs are blocked for more than 4 hours, post a status update in Discussions.
+5. Root-cause. Common suspects: action version drift, JDK EA breakage, Maven cache corruption, CodeQL rule updates.
+6. Fix in a focused PR; require a green run on a throwaway branch before merging.
+7. Re-enable the workflow.
+8. Escalate to SEV-1 only if the incident is blocking a pending release.
+
+## Yanked-release procedure
+
+Maven Central is **immutable**: once an artifact is published, it cannot be deleted. When a release must be withdrawn, this is the procedure:
+
+1. Publish a superseding version (e.g. `1.6.4` to replace a bad `1.6.3`) as soon as a fix or clean rebuild is ready.
+2. If the yank is security-related, open or update the corresponding GitHub Security Advisory (GHSA) for the bad version — the GHSA is the machine-readable record of the security flaw. For non-security yanks (e.g. a broken build regression), skip this step; the `readme.md` banner and blog post (steps 3–4) are the durable record.
+3. Add a short banner to `readme.md` listing the yanked version, the reason, and the replacement — e.g. *"⚠ MapStruct 1.6.3 is yanked due to [reason]. Please upgrade to 1.6.4."*
+4. Post a notice on the mapstruct.org blog and link it from Discussions.
+5. Remove the banner from `readme.md` at the next normal release cycle (the GHSA, if opened, is the durable record for security yanks; the blog post is the durable record for non-security yanks).
+
+## Secrets and rotation
+
+The IRP itself does not store any secret. This section is the **index** of which secrets drive releases and CI, so that a rotation during an incident hits everything in one pass. The live rotation credentials and step-by-step procedures live outside the repo in the maintainers' password manager.
+
+Secrets used by `.github/workflows/release.yml`:
+
+| Secret | Purpose | Rotation path |
+|---|---|---|
+| `GPG_PASSPHRASE` | Passphrase for the release signing key | Change on a local keyring, update the repo secret, import new passphrase |
+| `GPG_PUBLIC_KEY` | ASCII-armored public key for JReleaser | Re-export from local keyring after rotation |
+| `GPG_PRIVATE_KEY` | ASCII-armored private key for JReleaser | Generate new key pair, re-export, revoke old key via keyserver |
+| `SONATYPE_CENTRAL_USERNAME` | New Sonatype Central Portal username | Rotate token in Sonatype Central Portal |
+| `SONATYPE_CENTRAL_TOKEN` | New Sonatype Central Portal token | Rotate token in Sonatype Central Portal |
+| `SONATYPE_USERNAME` | Legacy OSSRH / Nexus2 username | Rotate in Sonatype JIRA / legacy portal |
+| `SONATYPE_PASSWORD` | Legacy OSSRH / Nexus2 password | Rotate in Sonatype JIRA / legacy portal |
+| `GIT_WEBSITE_ACCESS_TOKEN` | PAT for pushing to `mapstruct/mapstruct.org` | Regenerate PAT on the owning account, update repo secret |
+
+Secrets used by `.github/workflows/main.yml`:
+
+| Secret | Purpose | Rotation path |
+|---|---|---|
+| `SONATYPE_USERNAME` / `SONATYPE_PASSWORD` | Snapshot deploys to OSSRH from `main` | Same as above |
+| `CODECOV_TOKEN` | Upload coverage to Codecov | Regenerate on codecov.io, update repo secret |
+
+Rotation order during a suspected compromise: **Sonatype first** (to stop any further publish), **GPG second** (revoke key publicly), **website PAT last** (lowest blast radius).
+
+## Communication channels map
+
+| Incident type | Private tracking | Public acknowledgement | Resolution announcement |
+|---|---|---|---|
+| Security / CVE | GitHub Private Advisory | Only after the fix is released | GHSA publish + Discussion post + mapstruct.org blog |
+| Critical bug | None (public) | Pinned GitHub issue within 24 hours | Issue closed + Discussion post |
+| Release / supply chain | Direct maintainer DM (Signal) | Pinned issue + mapstruct.org banner | Discussion post + mapstruct.org blog |
+| Infra / CI | None | Discussion thread if contributor-blocking | Close the discussion |
+
+## Maintenance of this document
+
+This document is reviewed annually and after every SEV-0 or SEV-1 incident. Changes are made via normal PRs.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000000..b6703e1800
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,45 @@
+name: Bug report
+description: Create a report and help us improve
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please fill in all required fields with as many details as possible.
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ description: |
+ Describe what you were expecting MapStruct to do
+ placeholder: |
+ Here you can also add the generated code that you would like MapStruct to generate
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual behavior
+ description: |
+ Describe what you observed MapStruct did instead
+ placeholder: |
+ Here you can also add the generated code that MapStruct generated
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce the problem
+ description: |
+ - Share your mapping configuration
+ - An [MCVE (Minimal Complete Verifiable Example)](https://stackoverflow.com/help/minimal-reproducible-example) can be helpful to provide a complete reproduction case
+ placeholder: |
+ Share your MapStruct configuration
+ validations:
+ required: true
+ - type: input
+ id: mapstruct-version
+ attributes:
+ label: MapStruct Version
+ description: |
+ Which MapStruct version did you use?
+ Note: While you can obviously continue using older versions of MapStruct, it may well be that your bug is already fixed. If you're using an older version, please also try to reproduce the bug in the latest version of MapStruct before reporting it.
+ placeholder: ex. MapStruct 1.5.2
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..5b7ced86a4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,16 @@
+contact_links:
+ - name: MapStruct Discussions
+ url: https://github.com/mapstruct/mapstruct/discussions
+ about: Please use the MapStruct GitHub Discussions for open ended discussions and to reach out to the community.
+ - name: Stack Overflow
+ url: https://stackoverflow.com/questions/tagged/mapstruct
+ about: For questions about how to use MapStruct, consider asking your question on Stack Overflow, tagged [mapstruct].
+ - name: Documentation
+ url: https://mapstruct.org/documentation/stable/reference/html/
+ about: The MapStruct reference documentation.
+ - name: Gitter Chat
+ url: https://gitter.im/mapstruct/mapstruct-users
+ about: For realtime communication with the MapStruct community, consider writing in our Gitter chat room.
+ - name: MapStruct Examples
+ url: https://github.com/mapstruct/mapstruct-examples
+ about: Some examples of what can be achieved with MapStruct. (contributions are always welcome)
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000000..191bba8345
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,43 @@
+name: Feature Request
+description: Suggest an idea
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Use case
+ description: |
+ Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
+ placeholder: Describe the use-case here
+ validations:
+ required: true
+ - type: textarea
+ id: solution
+ attributes:
+ label: Generated Code
+ description: |
+ Please describe the possible generated code you'd like to see in MapStruct generate.
+ Please note, it's not always easy to describe a good solution. Describing the use-case above is much more important to us.
+ placeholder: Describe the possible solution here.
+ validations:
+ required: false
+ - type: textarea
+ id: workarounds
+ attributes:
+ label: Possible workarounds
+ description: |
+ Please describe the possible workarounds you've implemented to work around the lacking functionality.
+ placeholder: Describe the possible workarounds here.
+ validations:
+ required: false
+ - type: input
+ id: mapstruct-version
+ attributes:
+ label: MapStruct Version
+ description: What MapStruct version and edition did you try?
+ placeholder: ex. MapStruct 1.5.2
+ validations:
+ required: false
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..f6faee6938
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/scripts/update-website.sh b/.github/scripts/update-website.sh
new file mode 100644
index 0000000000..7c92b8b43b
--- /dev/null
+++ b/.github/scripts/update-website.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+#
+# Copyright MapStruct Authors.
+#
+# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+# env vars:
+# VERSION
+# GH_BOT_EMAIL
+
+# This script has been inspired by the JReleaser update-website.sh (https://github.com/jreleaser/jreleaser/blob/main/.github/scripts/update-website.sh)
+set -e
+
+function computePlainVersion() {
+ echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2.\3/'
+}
+
+function computeMajorMinorVersion() {
+ echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2/'
+}
+
+function isStable() {
+ local PLAIN_VERSION=$(computePlainVersion $1)
+ if [ "${PLAIN_VERSION}" == "$1" ]; then
+ echo "yes"
+ else
+ echo "no"
+ fi
+}
+
+STABLE=$(isStable $VERSION)
+MAJOR_MINOR_VERSION=$(computeMajorMinorVersion $VERSION)
+
+DEV_VERSION=`grep devVersion config.toml | sed 's/.*"\(.*\)"/\1/'`
+MAJOR_MINOR_DEV_VERSION=$(computeMajorMinorVersion $DEV_VERSION)
+STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'`
+MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION)
+
+echo "📝 Updating versions"
+
+SEDOPTION="-i"
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ SEDOPTION="-i ''"
+fi
+
+sed $SEDOPTION -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml
+
+if [ "${STABLE}" == "yes" ]; then
+ sed $SEDOPTION -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml
+ if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then
+ echo "📝 Updating new stable version"
+ # This means that we have a new stable version and we need to change the order of the releases.
+ sed $SEDOPTION -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml
+ NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2))
+ sed $SEDOPTION -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml
+ git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml
+ fi
+elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then
+ echo "📝 Updating new dev version"
+ # This means that we are updating for a new dev version, but the last dev version is not the one that we are doing.
+ # Therefore, we need to update add the new data configuration
+ cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml
+ sed $SEDOPTION -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml
+fi
+
+sed $SEDOPTION -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml
+sed $SEDOPTION -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml
+git add data/releases/${MAJOR_MINOR_VERSION}.toml
+git add config.toml
+
+echo "📝 Updating distribution resources"
+tar -xf tmp/mapstruct-${VERSION}-dist.tar.gz --directory tmp
+rm -rf static/documentation/${MAJOR_MINOR_VERSION}
+cp -R tmp/mapstruct-${VERSION}/docs static/documentation/${MAJOR_MINOR_VERSION}
+mv static/documentation/${MAJOR_MINOR_VERSION}/reference/html/mapstruct-reference-guide.html static/documentation/${MAJOR_MINOR_VERSION}/reference/html/index.html
+git add static/documentation/${MAJOR_MINOR_VERSION}
+
+git config --global user.email "${GH_BOT_EMAIL}"
+git config --global user.name "GitHub Action"
+git commit -a -m "Releasing version ${VERSION}"
+git push
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..0c6333a8ff
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,45 @@
+name: CodeQL
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+ schedule:
+ - cron: '37 23 * * 6'
+
+permissions: {}
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - language: actions
+ build-mode: none
+ - language: java-kotlin
+ build-mode: autobuild
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ - name: Set up Java
+ if: matrix.language == 'java-kotlin'
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ with:
+ distribution: 'zulu'
+ java-version: '21'
+ cache: 'maven'
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml
index 598a8979a6..04897b2156 100644
--- a/.github/workflows/java-ea.yml
+++ b/.github/workflows/java-ea.yml
@@ -2,23 +2,23 @@ name: Java EA
on: [push]
+permissions:
+ contents: read
+
env:
MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
jobs:
test_jdk_ea:
- strategy:
- fail-fast: false
- matrix:
- java: [17-ea]
- name: 'Linux JDK ${{ matrix.java }}'
+ name: 'Linux JDK EA'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
- uses: actions/checkout@v2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: 'Set up JDK'
- uses: actions/setup-java@v1
+ uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0
with:
- java-version: ${{ matrix.java }}
+ website: jdk.java.net
+ release: EA
- name: 'Test'
- run: ./mvnw ${MAVEN_ARGS} install -DskipDistribution=true
+ run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000000..865820f6ab
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,23 @@
+name: Mac OS CI
+
+on: push
+
+permissions:
+ contents: read
+
+env:
+ MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
+
+jobs:
+ mac:
+ name: 'Mac OS'
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - name: 'Set up JDK 21'
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ with:
+ distribution: 'zulu'
+ java-version: 21
+ - name: 'Test'
+ run: ./mvnw ${MAVEN_ARGS} install
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 741f8bce61..d1ebef4642 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -2,6 +2,9 @@ name: CI
on: [push, pull_request]
+permissions:
+ contents: read
+
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
@@ -12,56 +15,52 @@ jobs:
strategy:
fail-fast: false
matrix:
- java: [11, 13, 16]
+ java: [21, 25, 26]
name: 'Linux JDK ${{ matrix.java }}'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
- uses: actions/checkout@v2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: 'Set up JDK'
- uses: actions/setup-java@v1
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
+ distribution: 'zulu'
java-version: ${{ matrix.java }}
- name: 'Test'
- run: ./mvnw ${MAVEN_ARGS} install -DskipDistribution=true
- linux:
- name: 'Linux JDK 8'
- runs-on: ubuntu-latest
- steps:
- - name: 'Checkout'
- uses: actions/checkout@v2
- - name: 'Set up JDK 8'
- uses: actions/setup-java@v1
- with:
- java-version: 8
- - name: 'Test'
- run: ./mvnw ${MAVEN_ARGS} install
+ run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }}
- name: 'Generate coverage report'
+ if: matrix.java == 21
run: ./mvnw jacoco:report
- name: 'Upload coverage to Codecov'
- uses: codecov/codecov-action@v1
+ if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct'
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+ with:
+ fail_ci_if_error: true
+ token: ${{ secrets.CODECOV_TOKEN }}
- name: 'Publish Snapshots'
- if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'mapstruct/mapstruct'
+ if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct'
run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy
- windows:
- name: 'Windows'
- runs-on: windows-latest
+ integration_test_jdk:
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [ 8, 17 ]
+ name: 'Linux JDK ${{ matrix.java }}'
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: 'Set up JDK 8'
- uses: actions/setup-java@v1
+ - name: 'Checkout'
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - name: 'Set up JDK 21 for building everything'
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
- java-version: 8
- - name: 'Test'
- run: ./mvnw ${MAVEN_ARGS} install
- mac:
- name: 'Mac OS'
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v2
- - name: 'Set up JDK 8'
- uses: actions/setup-java@v1
+ distribution: 'zulu'
+ java-version: 21
+ - name: 'Install Processor'
+ run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am
+ - name: 'Set up JDK ${{ matrix.java }} for running integration tests'
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
- java-version: 8
- - name: 'Test'
- run: ./mvnw ${MAVEN_ARGS} install
+ distribution: 'zulu'
+ java-version: ${{ matrix.java }}
+ - name: 'Run integration tests'
+ run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..58440dcebd
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,124 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release version'
+ required: true
+ next:
+ description: 'Next version'
+ required: false
+
+jobs:
+ release:
+ # This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml)
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ issues: write
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java
+ uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
+ with:
+ java-version: 21
+ distribution: 'zulu'
+ cache: maven
+
+ - name: Set release version
+ id: version
+ run: |
+ RELEASE_VERSION=${{ github.event.inputs.version }}
+ NEXT_VERSION=${{ github.event.inputs.next }}
+ PLAIN_VERSION=`echo ${RELEASE_VERSION} | awk 'match($0, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/) { print substr($0, RSTART, RLENGTH); }'`
+ COMPUTED_NEXT_VERSION="${PLAIN_VERSION}-SNAPSHOT"
+ if [ -z $NEXT_VERSION ]
+ then
+ NEXT_VERSION=$COMPUTED_NEXT_VERSION
+ fi
+ ./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false
+ git config --global user.email "${{ vars.GH_BOT_EMAIL }}"
+ git config --global user.name "GitHub Action"
+ git commit -a -m "Releasing version $RELEASE_VERSION"
+ git push
+ echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
+ echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_ENV
+ echo "PLAIN_VERSION=$PLAIN_VERSION" >> $GITHUB_ENV
+
+ - name: Stage
+ run: |
+ export GPG_TTY=$(tty)
+ ./mvnw -ntp -B --file pom.xml \
+ -Dmaven.site.skip=true -Drelease=true -Ppublication,stage
+
+ - name: Release
+ env:
+ JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+ JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
+ JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_CENTRAL_USERNAME }}
+ JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN: ${{ secrets.SONATYPE_CENTRAL_TOKEN }}
+ JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
+ JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
+ run: |
+ ./mvnw -ntp -B --file pom.xml -pl :mapstruct-parent -Pjreleaser jreleaser:release
+
+ - name: JReleaser output
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: jreleaser-release
+ path: |
+ parent/target/jreleaser/trace.log
+ parent/target/jreleaser/output.properties
+
+ - name: Reset NEXT_RELEASE_CHANGELOG.md
+ run: |
+ echo -e "### Features\n\n### Enhancements\n\n### Bugs\n\n### Documentation\n\n### Build\n" > NEXT_RELEASE_CHANGELOG.md
+
+ - name: Set next version
+ run: |
+ ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false
+ sed -i -e "s@project.build.outputTimestamp>.*\${git.commit.author.time}
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml
index a4591c39de..b15e108c5c 100644
--- a/build-config/src/main/resources/build-config/checkstyle.xml
+++ b/build-config/src/main/resources/build-config/checkstyle.xml
@@ -29,7 +29,9 @@
-
+
+
+
@@ -118,6 +120,14 @@
+
+
+
+
+
+
+
@@ -139,12 +149,14 @@
-
+
-
+
+
+
diff --git a/copyright.txt b/copyright.txt
index 01b6526c70..74f18d89b1 100644
--- a/copyright.txt
+++ b/copyright.txt
@@ -1,18 +1,24 @@
Contributors
============
+Aleksey Ivashin - https://github.com/xumk
Alexandr Shalugin - https://github.com/shalugin
Amine Touzani - https://github.com/ttzn
Andreas Gudian - https://github.com/agudian
Andrei Arlou - https://github.com/Captain1653
Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon
Arne Seime - https://github.com/seime
+Burak Yildirim - https://github.com/bydrim
+Cause Chung - https://github.com/cuzfrog
Christian Bandowski - https://github.com/chris922
+Chris DeLashmutt - https://github.com/cdelashmutt-pivotal
+Christian Kosmowski - https://github.com/ckosmowski
Christian Schuster - https://github.com/chschu
Christophe Labouisse - https://github.com/ggtools
Ciaran Liedeman - https://github.com/cliedeman
Cindy Wang - https://github.com/birdfriend
Cornelius Dirmeier - https://github.com/cornzy
+Dennis Melzer - https://github.com/
David Feinblum - https://github.com/dvfeinblum
Darren Rambaud - https://github.com/xyzst
Dekel Pilli - https://github.com/dekelpilli
@@ -26,40 +32,64 @@ Florian Tavares - https://github.com/neoXfire
Gervais Blaise - https://github.com/gervaisb
Gibou Damien - https://github.com/dmngb
Gunnar Morling - https://github.com/gunnarmorling
+hduelme - https://github.com/hduelme
Ivo Smid - https://github.com/bedla
Jason Bodnar - https://github.com/Blackbaud-JasonBodnar
Jeroen van Wilgenburg - https://github.com/jvwilge
Jeff Smyth - https://github.com/smythie86
+jmwbRyDWLeNsvtzrihGoY - https://github.com/jmwbRyDWLeNsvtzrihGoY
João Paulo Bassinello - https://github.com/jpbassinello
Jonathan Kraska - https://github.com/jakraska
Joshua Spoerri - https://github.com/spoerri
Jude Niroshan - https://github.com/JudeNiroshan
+Justyna Kubica-Ledzion - https://github.com/JKLedzion
Kemal Özcan - https://github.com/yekeoe
+Ken Wang - https://github.com/ro0sterjam
Kevin Grüneberg - https://github.com/kevcodez
Lukas Lazar - https://github.com/LukeLaz
Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis
+Maciej Kucharczyk - https://github.com/mk868
Michael Pardo - https://github.com/pardom
+Muhammad Usama - https://github.com/the-mgi
Mustafa Caylak - https://github.com/luxmeter
+Neale Upstone - https://github.com/nealeu
Oliver Ehrenmüller - https://github.com/greuelpirat
+Oliver Erhart - https://github.com/thunderhook
Paul Strugnell - https://github.com/ps-powa
Pascal Grün - https://github.com/pascalgn
Pavel Makhov - https://github.com/streetturtle
Peter Larson - https://github.com/pjlarson
+Raimund Klein - https://github.com/Chessray
Remko Plantenga - https://github.com/sonata82
Remo Meier - https://github.com/remmeier
+Roel Mangelschots - https://github.com/rmschots
+Ritesh Chopade - https://github.com/codeswithritesh
Richard Lea - https://github.com/chigix
+Roman Obolonskyi - https://github.com/Obolrom
+Samil Can - https://github.com/SamilCan
Saheb Preet Singh - https://github.com/sahebpreet
Samuel Wright - https://github.com/samwright
Sebastian Haberey - https://github.com/sebastianhaberey
Sebastian Hasait - https://github.com/shasait
Sean Huang - https://github.com/seanjob
+seonwoojung - https://github.com/seonwooj0810
Sjaak Derksen - https://github.com/sjaakd
Stefan May - https://github.com/osthus-sm
+Takch02 - https://github.com/Takch02
Taras Mychaskiw - https://github.com/twentylemon
Thibault Duperron - https://github.com/Zomzog
Tomáš Poledný - https://github.com/Saljack
Tobias Meggendorfer - https://github.com/incaseoftrouble
+Tran Ngoc Nhan - https://github.com/
Tillmann Gaida - https://github.com/Tillerino
Timo Eckhardt - https://github.com/timoe
Tomek Gubala - https://github.com/vgtworld
+Valentin Kulesh - https://github.com/unshare
Vincent Alexander Beelte - https://github.com/grandmasterpixel
+Winter Andreas - https://github.dev/wandi34
+Xiu Hong Kooi - https://github.com/kooixh
+Yang Tang - https://github.com/tangyang9464
+Yevhen Vasyliev - https://github.com/yvasyliev
+Zegveld - https://github.com/Zegveld
+znight1020 - https://github.com/znight1020
+zral - https://github.com/zyberzebra
diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml
index 293043e6f9..c73676192b 100644
--- a/core-jdk8/pom.xml
+++ b/core-jdk8/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
diff --git a/core/pom.xml b/core/pom.xml
index 10c95cf3b5..e62d5e4682 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
diff --git a/core/src/main/java/org/mapstruct/AnnotateWith.java b/core/src/main/java/org/mapstruct/AnnotateWith.java
new file mode 100644
index 0000000000..79b7cec98c
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/AnnotateWith.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * This can be used to have mapstruct generate additional annotations on classes/methods.
+ *
+ * Examples based on the spring framework annotations.
+ *
+ *
+ * The following code would be generated:
+ *
+ *
+ * @Profile( value = "prod" )
+ * public class FooMapperImpl implements FooMapper {
+ * // mapper code
+ * }
+ *
+ *
+ * @author Ben Zegveld
+ * @since 1.6
+ */
+@Repeatable( AnnotateWiths.class )
+@Retention( CLASS )
+@Target( { TYPE, METHOD, ANNOTATION_TYPE } )
+public @interface AnnotateWith {
+
+ /**
+ * The annotation class that needs to be added.
+ *
+ * @return the annotation class that needs to be added.
+ */
+ Class extends Annotation> value();
+
+ /**
+ * The annotation elements that are to be applied to the annotation that should be added.
+ *
+ * @return the annotation elements that are to be applied to this annotation.
+ */
+ Element[] elements() default {};
+
+ /**
+ * Used in combination with {@link AnnotateWith} to configure the annotation elements. Only 1 value type may be used
+ * within the same annotation at a time. For example mixing shorts and ints is not allowed.
+ *
+ * @author Ben Zegveld
+ * @since 1.6
+ */
+ @interface Element {
+ /**
+ * The name of the annotation element.
+ *
+ * @return name of the annotation element.
+ */
+ String name() default "value";
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return short value(s) for the annotation element.
+ */
+ short[] shorts() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return byte value(s) for the annotation element.
+ */
+ byte[] bytes() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return int value(s) for the annotation element.
+ */
+ int[] ints() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return long value(s) for the annotation element.
+ */
+ long[] longs() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return float value(s) for the annotation element.
+ */
+ float[] floats() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return double value(s) for the annotation element.
+ */
+ double[] doubles() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return char value(s) for the annotation element.
+ */
+ char[] chars() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return boolean value(s) for the annotation element.
+ */
+ boolean[] booleans() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return string value(s) for the annotation element.
+ */
+ String[] strings() default {};
+
+ /**
+ * cannot be used in conjunction with other value fields.
+ *
+ * @return class value(s) for the annotation element.
+ */
+ Class>[] classes() default {};
+
+ /**
+ * only used in conjunction with the {@link #enums()} annotation element.
+ *
+ * @return the class of the enum.
+ */
+ Class extends Enum>> enumClass() default NullEnum.class;
+
+ /**
+ * cannot be used in conjunction with other value fields. {@link #enumClass()} is also required when using
+ * {@link #enums()}
+ *
+ * @return enum value(s) for the annotation element.
+ */
+ String[] enums() default {};
+
+ }
+}
diff --git a/core/src/main/java/org/mapstruct/AnnotateWiths.java b/core/src/main/java/org/mapstruct/AnnotateWiths.java
new file mode 100644
index 0000000000..5e86ad9a19
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/AnnotateWiths.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * This can be used to have mapstruct generate additional annotations on classes/methods.
+ *
+ * @author Ben Zegveld
+ * @since 1.6
+ */
+@Retention( CLASS )
+@Target( { TYPE, METHOD } )
+public @interface AnnotateWiths {
+
+ /**
+ * The configuration of the additional annotations.
+ *
+ * @return The configuration of the additional annotations.
+ */
+ AnnotateWith[] value();
+}
diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java
index f8c749fbde..309458f861 100644
--- a/core/src/main/java/org/mapstruct/BeanMapping.java
+++ b/core/src/main/java/org/mapstruct/BeanMapping.java
@@ -14,10 +14,13 @@
import org.mapstruct.control.MappingControl;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
+import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Configures the mapping between two bean types.
*
+ * Unless otherwise specified these properties are inherited to the generated bean mapping methods.
+ *
* Either {@link #resultType()}, {@link #qualifiedBy()} or {@link #nullValueMappingStrategy()} must be specified.
*
*
Example: Determining the result type
@@ -57,6 +60,8 @@
/**
* Specifies the result type of the factory method to be used in case several factory methods qualify.
+ *
+ * NOTE: This property is not inherited to generated mapping methods
*
* @return the resultType to select
*/
@@ -116,9 +121,32 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
+ /**
+ * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
+ *
+ * Overrides the setting on {@link MapperConfig} and {@link Mapper}.
+ *
+ * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
+ *
+ * @since 1.5
+ */
+ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
+
+ /**
+ * Specifies the exception type to be thrown when a missing subclass implementation is detected
+ * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
+ *
+ * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
+ * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
+ *
+ * @return the exception class to throw when missing implementations are found.
+ * Defaults to {@link IllegalArgumentException}.
+ */
+ Class extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
+
/**
* Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No
- * warning will be issued on missing target properties.
+ * warning will be issued on missing source or target properties.
*
* @return The ignore strategy (default false).
*
@@ -133,6 +161,8 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
* source properties report.
*
* NOTE: This does not support ignoring nested source properties
+ *
+ * NOTE: This property is not inherited to generated mapping methods
*
* @return The source properties that should be ignored when performing a report
*
@@ -140,6 +170,17 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
*/
String[] ignoreUnmappedSourceProperties() default {};
+ /**
+ * How unmapped properties of the source type of a mapping should be reported.
+ * If no policy is configured, the policy given via {@link MapperConfig#unmappedSourcePolicy()} or
+ * {@link Mapper#unmappedSourcePolicy()} will be applied, using {@link ReportingPolicy#IGNORE} by default.
+ *
+ * @return The reporting policy for unmapped source properties.
+ *
+ * @since 1.6
+ */
+ ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE;
+
/**
* How unmapped properties of the target type of a mapping should be reported.
* If no policy is configured, the policy given via {@link MapperConfig#unmappedTargetPolicy()} or
diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java
new file mode 100644
index 0000000000..c533b1ea32
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+/**
+ * Determines whether a generated Mapper implementation class will be declared {@code public} or not.
+ *
+ * @author Raimund Klein
+ *
+ * @since 1.7.0
+ */
+public enum ClassAccessibility {
+ /**
+ * The generated Mapper will have the same modifier ({@code public} or none) as the annotated class or interface.
+ */
+ DEFAULT,
+ /**
+ * The generated Mapper will be declared {@code public}.
+ */
+ PUBLIC,
+ /**
+ * The generated Mapper will have no visibility modifier ("package-private").
+ */
+ PACKAGE_PRIVATE
+}
diff --git a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
index afaba97370..0ea3ee7df5 100644
--- a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
+++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java
@@ -7,6 +7,54 @@
/**
* Strategy for propagating the value of collection-typed properties from source to target.
+ *
+ * In the table below, the dash {@code -} indicates a property name.
+ * Next, the trailing {@code s} indicates the plural form.
+ * The table explains the options and how they are applied to the presence / absence of a
+ * {@code set-s}, {@code add-} and / or {@code get-s} method on the target object.
+ *
+ *
Collection mapping strategy options
+ *
+ *
Option
+ *
Only target set-s Available
+ *
Only target add- Available
+ *
Both set-s/add- Available
+ *
No set-s/add- Available
+ *
Existing Target ({@code @TargetType})
+ *
+ *
+ *
{@link #ACCESSOR_ONLY}
+ *
set-s
+ *
get-s
+ *
set-s
+ *
get-s
+ *
get-s
+ *
+ *
+ *
{@link #SETTER_PREFERRED}
+ *
set-s
+ *
add-
+ *
set-s
+ *
get-s
+ *
get-s
+ *
+ *
+ *
{@link #ADDER_PREFERRED}
+ *
set-s
+ *
add-
+ *
add-
+ *
get-s
+ *
get-s
+ *
+ *
+ *
{@link #TARGET_IMMUTABLE}
+ *
set-s
+ *
exception
+ *
set-s
+ *
exception
+ *
set-s
+ *
+ *
*
* @author Sjaak Derksen
*/
diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java
index ec1bf06a88..8abe2f817c 100644
--- a/core/src/main/java/org/mapstruct/Condition.java
+++ b/core/src/main/java/org/mapstruct/Condition.java
@@ -11,24 +11,35 @@
import java.lang.annotation.Target;
/**
- * This annotation marks a method as a presence check method to check check for presence in beans.
+ * This annotation marks a method as a presence check method to check for presence in beans
+ * or it can be used to define additional check methods for something like source parameters.
*
- * By default bean properties are checked against {@code null} or using a presence check method in the source bean.
+ * By default, bean properties are checked against {@code null} or using a presence check method in the source bean.
* If a presence check method is available then it will be used instead.
*
* Presence check methods have to return {@code boolean}.
* The following parameters are accepted for the presence check methods:
*
*
The parameter with the value of the source property.
- * e.g. the value given by calling {@code getName()} for the name property of the source bean
+ * e.g. the value given by calling {@code getName()} for the name property of the source bean
+ * - only possible when using the {@link ConditionStrategy#PROPERTIES}
+ *
*
The mapping source parameter
*
{@code @}{@link Context} parameter
+ *
+ * {@code @}{@link TargetPropertyName} parameter -
+ * only possible when using the {@link ConditionStrategy#PROPERTIES}
+ *
+ *
+ * {@code @}{@link SourcePropertyName} parameter -
+ * only possible when using the {@link ConditionStrategy#PROPERTIES}
+ *
*
*
* Note: The usage of this annotation is mandatory
* for a method to be considered as a presence check method.
*
- *
+ *
* public class PresenceCheckUtils {
*
* @Condition
@@ -43,11 +54,10 @@
* MovieDto map(Movie movie);
* }
*
- *
+ *
* The following implementation of {@code MovieMapper} will be generated:
*
- *
+ * This annotation can also be used as a meta-annotation to define the condition strategy.
*
* @author Filip Hrisafov
+ * @see SourceParameterCondition
* @since 1.5
*/
-@Target({ ElementType.METHOD })
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Condition {
+ /**
+ * The condition strategy for the condition.
+ * This determines whether the condition is applied to properties, parameters, or both.
+ *
+ * @return the places where the condition should apply to
+ * @since 1.6
+ */
+ ConditionStrategy[] appliesTo() default ConditionStrategy.PROPERTIES;
+
}
diff --git a/core/src/main/java/org/mapstruct/ConditionStrategy.java b/core/src/main/java/org/mapstruct/ConditionStrategy.java
new file mode 100644
index 0000000000..6b042017c2
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/ConditionStrategy.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+/**
+ * Strategy for defining what to what a condition (check) method is applied to
+ *
+ * @author Filip Hrisafov
+ * @since 1.6
+ */
+public enum ConditionStrategy {
+ /**
+ * The condition method should be applied whether a property should be mapped.
+ */
+ PROPERTIES,
+ /**
+ * The condition method should be applied to check if a source parameters should be mapped.
+ */
+ SOURCE_PARAMETERS,
+}
diff --git a/core/src/main/java/org/mapstruct/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java
index 78b2c580ff..3db27b2a0a 100644
--- a/core/src/main/java/org/mapstruct/DecoratedWith.java
+++ b/core/src/main/java/org/mapstruct/DecoratedWith.java
@@ -103,12 +103,12 @@
* private PersonMapper personMapper; // injects the decorator, with the injected original mapper
*
*
- *
3. Component model 'jsr330'
+ *
3. Component model 'jsr330' or 'jakarta'
*
Referencing the original mapper
*
- * JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated
- * implementation of the original mapper is annotated with
- * {@code @javax.inject.Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when
+ * JSR 330 / Jakarta Inject doesn't specify qualifiers and only allows to specifically name the beans. Hence,
+ * the generated implementation of the original mapper is annotated with
+ * {@code @Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when
* using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your
* decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class):
*
@@ -140,12 +140,11 @@
* @javax.inject.Named
* private PersonMapper personMapper; // injects the decorator, with the injected original mapper
*
- *
*
* @author Gunnar Morling
*/
@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.SOURCE)
+@Retention(RetentionPolicy.CLASS)
public @interface DecoratedWith {
/**
diff --git a/core/src/main/java/org/mapstruct/EnumMapping.java b/core/src/main/java/org/mapstruct/EnumMapping.java
index ba3a5e0cb0..375f969b01 100644
--- a/core/src/main/java/org/mapstruct/EnumMapping.java
+++ b/core/src/main/java/org/mapstruct/EnumMapping.java
@@ -98,7 +98,7 @@
*
*
{@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* suffix to the source enum
- *
{@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the the given {@link #configuration()}
+ *
{@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the given {@link #configuration()}
* from the end of the source enum
*
{@link MappingConstants#PREFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* prefix to the source enum
diff --git a/core/src/main/java/org/mapstruct/Ignored.java b/core/src/main/java/org/mapstruct/Ignored.java
new file mode 100644
index 0000000000..47e4961f43
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/Ignored.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Configures the ignored of one bean attribute.
+ *
+ *
+ * The name all attributes of for ignored is to be specified via {@link #targets()}.
+ *
+ *
+ *
+ * Example 1: Implicitly mapping fields with the same name:
+ *
+ *
+ *
+ * // We need ignored Human.name and Human.lastName
+ * // we can use @Ignored with parameters "name", "lastName" {@link #targets()}
+ * @Mapper
+ * public interface HumanMapper {
+ * @Ignored( targets = { "name", "lastName" } )
+ * HumanDto toHumanDto(Human human)
+ * }
+ *
+ *
+ * @author Ivashin Aleksey
+ */
+@Repeatable(IgnoredList.class)
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface Ignored {
+
+ /**
+ * Whether the specified properties should be ignored by the generated mapping method.
+ * This can be useful when certain attributes should not be propagated from source to target or when properties in
+ * the target object are populated using a decorator and thus would be reported as unmapped target property by
+ * default.
+ *
+ * @return The target names of the configured properties that should be ignored
+ */
+ String[] targets();
+
+ /**
+ * The prefix that should be applied to all the properties specified via {@link #targets()}.
+ *
+ * @return The target prefix to be applied to the defined properties
+ */
+ String prefix() default "";
+
+}
diff --git a/core/src/main/java/org/mapstruct/IgnoredList.java b/core/src/main/java/org/mapstruct/IgnoredList.java
new file mode 100644
index 0000000000..e47db6bac7
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/IgnoredList.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Configures the ignored list for several bean attributes.
+ *
+ * TIP: When using Java 8 or later, you can omit the {@code @IgnoredList}
+ * wrapper annotation and directly specify several {@code @Ignored} annotations on one method.
+ *
+ *
+ *
+ * @author Ivashin Aleksey
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+public @interface IgnoredList {
+
+ /**
+ * The configuration of the bean attributes.
+ *
+ * @return The configuration of the bean attributes.
+ */
+ Ignored[] value();
+}
diff --git a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java
index ac582a5dfa..b659b7f37a 100644
--- a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java
+++ b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java
@@ -81,8 +81,8 @@
public @interface InheritInverseConfiguration {
/**
- * The name of the inverse mapping method to inherit the mappings from. Needs only to be specified in case more than
- * one inverse method with matching source and target type exists.
+ * The name of the inverse mapping method to inherit the mappings from. Needs to be specified only in case more than
+ * one inverse method exists with a matching source and target type exists.
*
* @return The name of the inverse mapping method to inherit the mappings from.
*/
diff --git a/core/src/main/java/org/mapstruct/InjectionStrategy.java b/core/src/main/java/org/mapstruct/InjectionStrategy.java
index 84baa8afa9..f5029e2246 100644
--- a/core/src/main/java/org/mapstruct/InjectionStrategy.java
+++ b/core/src/main/java/org/mapstruct/InjectionStrategy.java
@@ -7,9 +7,10 @@
/**
* Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and
- * JSR330.
+ * JSR330 / Jakarta.
*
* @author Kevin Grüneberg
+ * @author Lucas Resch
*/
public enum InjectionStrategy {
@@ -17,5 +18,8 @@ public enum InjectionStrategy {
FIELD,
/** Annotations are written on the constructor **/
- CONSTRUCTOR
+ CONSTRUCTOR,
+
+ /** A dedicated setter method is created */
+ SETTER
}
diff --git a/core/src/main/java/org/mapstruct/IterableMapping.java b/core/src/main/java/org/mapstruct/IterableMapping.java
index 76153127bd..d644dfe03b 100644
--- a/core/src/main/java/org/mapstruct/IterableMapping.java
+++ b/core/src/main/java/org/mapstruct/IterableMapping.java
@@ -66,19 +66,38 @@
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from an iterable of
* {@code String} to an iterable {@link Date} or vice-versa. Will be ignored for all other element types.
+ *
+ * If the {@link #locale()} is also specified, the format will consider the specified locale when processing
+ * the date. Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
+ * @see #locale()
*/
String dateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types.
+ *
+ * If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale.
+ * Otherwise, the system's default locale will be used to process the number format.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
+ * @see #locale()
*/
String numberFormat() default "";
+ /**
+ * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}.
+ *
+ * The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc.
+ *
+ * If no locale is specified, the system's default locale will be used.
+ *
+ * @return A string representing the locale to be used when formatting dates or numbers.
+ */
+ String locale() default "";
+
/**
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
* mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' error.
diff --git a/core/src/main/java/org/mapstruct/Javadoc.java b/core/src/main/java/org/mapstruct/Javadoc.java
new file mode 100644
index 0000000000..4b5d2fb839
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/Javadoc.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Allows the definition of Javadoc comments in the MapStruct Mapper generated class.
+ *
+ *
The annotation provides support for the usual Javadoc comments elements by defining analogous attributes.
+ *
+ *
+ *
Please, note that at least one of these attributes must be specified.
+ *
+ *
+ * For instance, the following definition;
+ *
+ *
+ * @Javadoc(
+ * value = "This is the description",
+ * authors = { "author1", "author2" },
+ * deprecated = "Use {@link OtherMapper} instead",
+ * since = "0.1"
+ * )
+ *
+ * // or using Text Blocks
+ * @Javadoc(
+ * """
+ * This is the description
+ *
+ * @author author1
+ * @author author2
+ *
+ * @deprecated Use {@link OtherMapper} instead
+ * @since 0.1
+ * """
+ * )
+ *
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Javadoc {
+ /**
+ * Main Javadoc comment text block.
+ *
+ * @return Main Javadoc comment text block.
+ */
+ String value() default "";
+
+ /**
+ * List of authors of the code that it is being documented.
+ *
+ * It will generate a list of the Javadoc tool comment element @author
+ * with the different values and in the order provided.
+ *
+ * @return array of javadoc authors.
+ */
+ String[] authors() default { };
+
+ /**
+ * Specifies that the functionality that is being documented is deprecated.
+ *
+ * Corresponds to the @deprecated Javadoc tool comment element.
+ *
+ * @return Deprecation message about the documented functionality
+ */
+ String deprecated() default "";
+
+ /**
+ * Specifies the version since the functionality that is being documented is available.
+ *
+ * Corresponds to the @since Javadoc tool comment element.
+ *
+ * @return Version since the functionality is available
+ */
+ String since() default "";
+}
diff --git a/core/src/main/java/org/mapstruct/MapMapping.java b/core/src/main/java/org/mapstruct/MapMapping.java
index 271272bb45..093099cf5a 100644
--- a/core/src/main/java/org/mapstruct/MapMapping.java
+++ b/core/src/main/java/org/mapstruct/MapMapping.java
@@ -56,8 +56,12 @@
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type
* {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types.
+ *
+ * If the {@link #locale()} is specified, the format will consider the specified locale when processing the date.
+ * Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
+ * @see #locale()
*/
String keyDateFormat() default "";
@@ -65,27 +69,50 @@
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value
* type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value
* types.
+ *
+ * If the {@link #locale()} is specified, the format will consider the specified locale when processing the date.
+ * Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
+ * @see #locale()
*/
String valueDateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other key types.
+ *
+ * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale.
+ * Otherwise, the system's default locale will be used.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
+ * @see #locale()
*/
String keyNumberFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other value types.
+ *
+ * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale.
+ * Otherwise, the system's default locale will be used.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
+ * @see #locale()
*/
String valueNumberFormat() default "";
+ /**
+ * Specifies the locale to be used when processing {@link SimpleDateFormat} or {@link DecimalFormat} for key or
+ * value mappings in maps. The locale should be a plain tag representing the language, such as "en" for English,
+ * "de" for German, etc.
+ *
+ * If no locale is specified, the system's default locale will be used.
+ *
+ * @return A string representing the locale to be used when formatting dates or numbers in maps.
+ */
+ String locale() default "";
+
/**
* A key value qualifier can be specified to aid the selection process of a suitable mapper. This is useful in
* case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found'
diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java
index 98022f1a6c..c502dfe8a9 100644
--- a/core/src/main/java/org/mapstruct/Mapper.java
+++ b/core/src/main/java/org/mapstruct/Mapper.java
@@ -14,7 +14,9 @@
import org.mapstruct.control.MappingControl;
import org.mapstruct.factory.Mappers;
+import static org.mapstruct.ClassAccessibility.DEFAULT;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
+import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via
@@ -73,6 +75,7 @@
*
*
* @author Gunnar Morling
+ * @see Javadoc
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@@ -141,7 +144,12 @@
* can be retrieved via {@code @Autowired}
*
* {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and
- * {@code @Singleton}, and can be retrieved via {@code @Inject}
+ * {@code @Singleton}, and can be retrieved via {@code @Inject}.
+ * The annotations will either be from javax.inject or jakarta.inject,
+ * depending on which one is available, with javax.inject having precedence.
+ *
+ * {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and
+ * {@code @Singleton}, and can be retrieved via {@code @Inject}.
*
* The method overrides a componentModel set in a central configuration set
* by {@link #config() }
@@ -202,6 +210,32 @@
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+ /**
+ * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping} of
+ * this mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither
+ * strategy is configured, the strategy given via {@link MapperConfig#nullValueIterableMappingStrategy()} will be
+ * applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default.
+ *
+ * @since 1.5
+ *
+ * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping} of
+ * this mapper.
+ */
+ NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+
+ /**
+ * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping} of this
+ * mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither strategy
+ * is configured, the strategy given via {@link MapperConfig#nullValueMapMappingStrategy()} will be applied, using
+ * {@link NullValueMappingStrategy#RETURN_NULL} by default.
+ *
+ * @since 1.5
+ *
+ * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping} of this
+ * mapper.
+ */
+ NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+
/**
* The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is
* configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} will be applied,
@@ -237,6 +271,29 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
+ /**
+ * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
+ *
+ * Can be overridden by the one on {@link BeanMapping}, but overrides {@link MapperConfig}.
+ *
+ * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
+ *
+ * @since 1.5
+ */
+ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
+
+ /**
+ * Specifies the exception type to be thrown when a missing subclass implementation is detected
+ * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
+ *
+ * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
+ * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
+ *
+ * @return the exception class to throw when missing implementations are found.
+ * Defaults to {@link IllegalArgumentException}.
+ */
+ Class extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
+
/**
* Determines whether to use field or constructor injection. This is only used on annotated based component models
* such as CDI, Spring and JSR 330.
@@ -255,7 +312,7 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
* Can be configured by the {@link MapperConfig#disableSubMappingMethodsGeneration()} as well.
*
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
- * mapstruct.org or
+ * mapstruct.org or
* github.com/mapstruct/mapstruct to share what problem you
* are facing with the automatic sub-mapping generation.
*
@@ -319,4 +376,26 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
* @since 1.4
*/
Class extends Exception> unexpectedValueMappingException() default IllegalArgumentException.class;
+
+ /**
+ * Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed.
+ * i.e. not be added.
+ *
+ * The method overrides the flag set in a central configuration set by {@link #config()}
+ * or through an annotation processor option.
+ *
+ * @return whether the addition of a timestamp should be suppressed
+ *
+ * @since 1.5
+ */
+ boolean suppressTimestampInGenerated() default false;
+
+ /**
+ * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper
+ * implementation. Default is to mirror the interface or abstract class annotated by this {@code Mapper}.
+ *
+ * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper
+ * implementation
+ */
+ ClassAccessibility accessibility() default DEFAULT;
}
diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java
index ecfd888ca2..a81e3e8d4f 100644
--- a/core/src/main/java/org/mapstruct/MapperConfig.java
+++ b/core/src/main/java/org/mapstruct/MapperConfig.java
@@ -14,7 +14,9 @@
import org.mapstruct.control.MappingControl;
import org.mapstruct.factory.Mappers;
+import static org.mapstruct.ClassAccessibility.DEFAULT;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
+import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Marks a class or interface as configuration source for generated mappers. This allows to share common configurations
@@ -130,7 +132,12 @@
* can be retrieved via {@code @Autowired}
*
* {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and
- * {@code @Singleton}, and can be retrieved via {@code @Inject}
+ * {@code @Singleton}, and can be retrieved via {@code @Inject}.
+ * The annotations will either be from javax.inject or jakarta.inject,
+ * depending on which one is available, with javax.inject having precedence.
+ *
+ * {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and
+ * {@code @Singleton}, and can be retrieved via {@code @Inject}.
*
*
* @return The component model for the generated mapper.
@@ -176,6 +183,28 @@
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+ /**
+ * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping}.
+ * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using
+ * {@link NullValueMappingStrategy#RETURN_NULL} by default.
+ *
+ * @since 1.5
+ *
+ * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping}.
+ */
+ NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+
+ /**
+ * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping}.
+ * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using
+ * {@link NullValueMappingStrategy#RETURN_NULL} by default.
+ *
+ * @since 1.5
+ *
+ * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping}.
+ */
+ NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
+
/**
* The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is
* configured, {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
@@ -210,6 +239,29 @@ MappingInheritanceStrategy mappingInheritanceStrategy()
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
+ /**
+ * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
+ *
+ * Can be overridden by the one on {@link BeanMapping} or {@link Mapper}.
+ *
+ * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
+ *
+ * @since 1.5
+ */
+ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
+
+ /**
+ * Specifies the exception type to be thrown when a missing subclass implementation is detected
+ * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
+ *
+ * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
+ * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
+ *
+ * @return the exception class to throw when missing implementations are found.
+ * Defaults to {@link IllegalArgumentException}.
+ */
+ Class extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
+
/**
* Determines whether to use field or constructor injection. This is only used on annotated based component models
* such as CDI, Spring and JSR 330.
@@ -230,7 +282,7 @@ MappingInheritanceStrategy mappingInheritanceStrategy()
* Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()}
*
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
- * mapstruct.org or
+ * mapstruct.org or
* github.com/mapstruct/mapstruct to share what problem you
* are facing with the automatic sub-mapping generation.
*
@@ -295,5 +347,25 @@ MappingInheritanceStrategy mappingInheritanceStrategy()
*/
Class extends Exception> unexpectedValueMappingException() default IllegalArgumentException.class;
+ /**
+ * Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed.
+ * i.e. not be added.
+ *
+ * The method overrides the flag set through an annotation processor option.
+ *
+ * @return whether the addition of a timestamp should be suppressed
+ *
+ * @since 1.5
+ */
+ boolean suppressTimestampInGenerated() default false;
+
+ /**
+ * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper
+ * implementation. Default is to mirror the interface or abstract class annotated by {@code Mapper}.
+ *
+ * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper
+ * implementation
+ */
+ ClassAccessibility accessibility() default DEFAULT;
}
diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java
index 0af73f3687..c2a6172e67 100644
--- a/core/src/main/java/org/mapstruct/Mapping.java
+++ b/core/src/main/java/org/mapstruct/Mapping.java
@@ -20,7 +20,7 @@
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/**
- * Configures the mapping of one bean attribute or enum constant.
+ * Configures the mapping of one bean attribute.
*
* The name of the mapped attribute or constant is to be specified via {@link #target()}. For mapped bean attributes it
* is assumed by default that the attribute has the same name in the source bean. Alternatively, one of
@@ -54,7 +54,7 @@
*
Example 2: Mapping properties with different names
*
* // We need map Human.companyName to HumanDto.company
- * // we can use @Mapping with parameters {@link #source()} and {@link #source()}
+ * // we can use @Mapping with parameters {@link #source()} and {@link #target()}
* @Mapper
* public interface HumanMapper {
* @Mapping(source="companyName", target="company")
@@ -118,7 +118,7 @@
* // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it
* @Mapper
* public interface HumanMapper {
- * @Mapping(source="name", target="name", defaultValue="Somebody")
+ * @Mapping(source="name", target="fullName", defaultValue="Somebody")
* HumanDto toHumanDto(Human human)
* }
*
@@ -136,9 +136,6 @@
* }
*
*
- * IMPORTANT NOTE: the enum mapping capability is deprecated and replaced by {@link ValueMapping} it
- * will be removed in subsequent versions.
- *
* @author Gunnar Morling
*/
@@ -178,19 +175,38 @@
/**
* A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to
* {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants.
+ *
+ * If the {@link #locale()} is also specified, the format will consider the specified locale when processing
+ * the date. Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
+ * @see #locale()
*/
String dateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types.
+ *
+ * If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale.
+ * Otherwise, the system's default locale will be used to process the number format.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
+ * @see #locale()
*/
String numberFormat() default "";
+ /**
+ * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}.
+ *
+ * The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc.
+ *
+ * If no locale is specified, the system's default locale will be used.
+ *
+ * @return A string representing the locale to be used when formatting dates or numbers.
+ */
+ String locale() default "";
+
/**
* A constant {@link String} based on which the specified target property is to be set.
*
@@ -214,10 +230,13 @@
*
* MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method,
* type conversion method or built-in conversion.
- *
*
*
*
+ * You can use {@link #qualifiedBy()} or {@link #qualifiedByName()} to force the use of a conversion method
+ * even when one would not apply. (e.g. {@code String} to {@code String})
+ *
+ *
* This attribute can not be used together with {@link #source()}, {@link #defaultValue()},
* {@link #defaultExpression()} or {@link #expression()}.
*
@@ -283,9 +302,12 @@
/**
* Whether the property specified via {@link #target()} should be ignored by the generated mapping method or not.
- * This can be useful when certain attributes should not be propagated from source or target or when properties in
+ * This can be useful when certain attributes should not be propagated from source to target or when properties in
* the target object are populated using a decorator and thus would be reported as unmapped target property by
* default.
+ *
+ * If you have multiple properties to ignore,
+ * you can use the {@link Ignored} annotation instead and group them all at once.
*
* @return {@code true} if the given property should be ignored, {@code false} otherwise
*/
@@ -295,6 +317,8 @@
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
* mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
* error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
+ *
+ * Note that {@link #defaultValue()} usage will also be converted using this qualifier.
*
* @return the qualifiers
* @see Qualifier
@@ -309,6 +333,8 @@
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
* number of qualifiers as no custom annotation types are needed.
+ *
+ * Note that {@link #defaultValue()} usage will also be converted using this qualifier.
*
* @return One or more qualifier name(s)
* @see #qualifiedBy()
@@ -419,13 +445,11 @@
* If not possible, MapStruct will try to apply a user defined mapping method.
*
*
- *
*
*
other
*
* MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method,
* type conversion method or built-in conversion.
- *
*
*
*
@@ -476,6 +500,4 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
*/
Class extends Annotation> mappingControl() default MappingControl.class;
-
-
}
diff --git a/core/src/main/java/org/mapstruct/MappingConstants.java b/core/src/main/java/org/mapstruct/MappingConstants.java
index 002f005299..3d3d8a4c77 100644
--- a/core/src/main/java/org/mapstruct/MappingConstants.java
+++ b/core/src/main/java/org/mapstruct/MappingConstants.java
@@ -109,7 +109,12 @@ private ComponentModel() {
public static final String DEFAULT = "default";
/**
- * The generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
+ * The generated mapper is an application-scoped CDI bean and can be retrieved via @Inject.
+ * The annotations are either from {@code javax} or {@code jakarta}.
+ * Priority have the {@code javax} annotations.
+ * In case you want to only use Jakarta then use {@link #JAKARTA_CDI}.
+ *
+ * @see #JAKARTA_CDI
*/
public static final String CDI = "cdi";
@@ -120,11 +125,30 @@ private ComponentModel() {
public static final String SPRING = "spring";
/**
- * The generated mapper is annotated with @javax.inject.Named and @Singleton, and can be retrieved via @Inject
+ * The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject.
+ * The annotations are either from {@code javax.inject} or {@code jakarta.inject}.
+ * Priority have the {@code javax.inject} annotations.
+ * In case you want to only use Jakarta then use {@link #JAKARTA}.
*
+ * @see #JAKARTA
*/
public static final String JSR330 = "jsr330";
+ /**
+ * The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject.
+ * The annotations are from {@code jakarta.inject}.
+ * In case you want to use {@code javax.inject} then use {@link #JSR330}.
+ *
+ * @see #JSR330
+ */
+ public static final String JAKARTA = "jakarta";
+
+ /**
+ * The generated mapper is an application-scoped Jakarta CDI bean and can be retrieved via @Inject.
+ * @see #CDI
+ */
+ public static final String JAKARTA_CDI = "jakarta-cdi";
+
}
}
diff --git a/core/src/main/java/org/mapstruct/NullEnum.java b/core/src/main/java/org/mapstruct/NullEnum.java
new file mode 100644
index 0000000000..ac39b3485d
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/NullEnum.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+/**
+ * To be used as a default value for enum class annotation elements.
+ *
+ * @author Ben Zegveld
+ * @since 1.6
+ */
+enum NullEnum {
+}
diff --git a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
index 446b879d92..22dba7c58b 100644
--- a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
+++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java
@@ -8,7 +8,7 @@
/**
* Strategy for dealing with null source values.
*
- * Note: This strategy is not in effect when the a specific source presence check method is defined
+ * Note: This strategy is not in effect when a specific source presence check method is defined
* in the service provider interface (SPI).
*
* Note: some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder
diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java
index 519a28bd81..4cece83031 100644
--- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java
+++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java
@@ -13,8 +13,9 @@
public enum NullValueMappingStrategy {
/**
- * If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no
- * alternative strategy is configured globally, for given mapper or method.
+ * If {@code null} is passed to a mapping method, {@code null} will be returned, unless the return type is
+ * {@link java.util.Optional Optional}, in which case an empty optional will be returned.
+ * That's the default behavior if no alternative strategy is configured globally, for given mapper or method.
*/
RETURN_NULL,
@@ -28,6 +29,7 @@ public enum NullValueMappingStrategy {
* case.
*
For iterable mapping methods an empty collection will be returned.
*
For map mapping methods an empty map will be returned.
+ *
For optional mapping methods an empty optional will be returned.
*
*/
RETURN_DEFAULT;
diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java
index 9e06e723b6..0ab30d5271 100644
--- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java
+++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java
@@ -10,7 +10,7 @@
* {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping}
* and {@link Mapping}.
* Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will
- * overide {@link Mapper}
+ * override {@link Mapper}
*
* The enum only applies to update methods: methods that update a pre-existing target (annotated with
* {@code @}{@link MappingTarget}).
@@ -27,7 +27,9 @@
public enum NullValuePropertyMappingStrategy {
/**
- * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}.
+ * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}
+ * or {@link java.util.Optional#empty() Optional.empty()} in case the target type is an
+ * {@link java.util.Optional Optional}.
*/
SET_TO_NULL,
@@ -36,6 +38,7 @@ public enum NullValuePropertyMappingStrategy {
*
* This means:
*
+ *
For {@code Optional} MapStruct generates an {@code Optional.empty()}
*
For {@code List} MapStruct generates an {@code ArrayList}
*
For {@code Map} a {@code HashMap}
*
For arrays an empty array
@@ -53,5 +56,12 @@ public enum NullValuePropertyMappingStrategy {
* If a source bean property equals {@code null} the target bean property will be ignored and retain its
* existing value.
*/
- IGNORE;
+ IGNORE,
+
+ /**
+ * If a source bean property equals {@code null} the target bean property will be cleared.
+ * This strategy is only applicable to target properties that are of type {@link java.util.Collection Collection}
+ * or {@link java.util.Map Map}.
+ */
+ CLEAR;
}
diff --git a/core/src/main/java/org/mapstruct/Qualifier.java b/core/src/main/java/org/mapstruct/Qualifier.java
index be51f49e04..9e18c3e420 100644
--- a/core/src/main/java/org/mapstruct/Qualifier.java
+++ b/core/src/main/java/org/mapstruct/Qualifier.java
@@ -21,6 +21,7 @@
*
{@link IterableMapping#qualifiedBy() }
*
{@link MapMapping#keyQualifiedBy() }
*
{@link MapMapping#valueQualifiedBy() }
+ *
{@link SubclassMapping#qualifiedBy() }
*
*
Example:
*
diff --git a/core/src/main/java/org/mapstruct/SourceParameterCondition.java b/core/src/main/java/org/mapstruct/SourceParameterCondition.java
new file mode 100644
index 0000000000..8bff97abc4
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SourceParameterCondition.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation marks a method as a check method to check if a source parameter needs to be mapped.
+ *
+ * By default, source parameters are checked against {@code null}, unless they are primitives.
+ *
+ * Check methods have to return {@code boolean}.
+ * The following parameters are accepted for the presence check methods:
+ *
+ *
The mapping source parameter
+ *
{@code @}{@link Context} parameter
+ *
+ *
+ * Note: The usage of this annotation is mandatory
+ * for a method to be considered as a source check method.
+ *
+ *
+ *
+ * @author Filip Hrisafov
+ * @since 1.6
+ * @see Condition @Condition
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.CLASS)
+@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+public @interface SourceParameterCondition {
+
+}
diff --git a/core/src/main/java/org/mapstruct/SourcePropertyName.java b/core/src/main/java/org/mapstruct/SourcePropertyName.java
new file mode 100644
index 0000000000..a9d036d5d8
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SourcePropertyName.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation marks a presence check method parameter as a source property name parameter.
+ *
+ * This parameter enables conditional filtering based on source property name at run-time.
+ * Parameter must be of type {@link String} and can be present only in {@link Condition} method.
+ *
+ *
+ * @author Oliver Erhart
+ * @since 1.6
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.CLASS)
+public @interface SourcePropertyName {
+}
diff --git a/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java b/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java
new file mode 100644
index 0000000000..a60d067faa
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+/**
+ * Strategy for dealing with subclassMapping annotated methods.
+ *
+ * @since 1.5
+ * @author Ben Zegveld
+ */
+public enum SubclassExhaustiveStrategy {
+
+ /**
+ * If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
+ * annotated mapping then a compilation error will be thrown.
+ */
+ COMPILE_ERROR,
+
+ /**
+ * If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
+ * annotated mapping then an {@link IllegalArgumentException} will be thrown if a call is made with a type for which
+ * there is no {@link SubclassMapping} available.
+ */
+ RUNTIME_EXCEPTION;
+}
diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java
new file mode 100644
index 0000000000..4d635d8aa3
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SubclassMapping.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.util.Experimental;
+
+/**
+ * Configures the mapping to handle hierarchy of the source type.
+ *
+ * The subclass to be mapped is to be specified via {@link #source()}.
+ * The subclass to map to is to be specified via {@link #target()}.
+ *
+ *
+ * This annotation can be combined with @Mapping annotations.
+ *
+ * Below follow examples of the implementation for the mapParent method.
+ * Example 1: For parents that cannot be created. (e.g. abstract classes or interfaces)
+ *
+ * // generates
+ * @Override
+ * public TargetParent mapParent(SourceParent parent) {
+ * if (parent instanceof SourceSubclass) {
+ * return mapSubclass( (SourceSubclass) parent );
+ * }
+ * else {
+ * throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for "
+ * + parent.getClass());
+ * }
+ * }
+ *
+ * Example 2: For parents that can be created. (e.g. normal classes or interfaces with
+ * @Mapper( uses = ObjectFactory.class ) )
+ *
+ *
+ * @author Ben Zegveld
+ * @since 1.5
+ */
+@Repeatable(value = SubclassMappings.class)
+@Retention(RetentionPolicy.CLASS)
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Experimental
+public @interface SubclassMapping {
+
+ /**
+ * The source subclass to check for before using the default mapping as fallback.
+ *
+ * @return the source subclass to check for before using the default mapping as fallback.
+ */
+ Class> source();
+
+ /**
+ * The target subclass to map the source to.
+ *
+ * @return the target subclass to map the source to.
+ */
+ Class> target();
+
+ /**
+ * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
+ * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
+ * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
+ *
+ * @return the qualifiers
+ * @see Qualifier
+ */
+ Class extends Annotation>[] qualifiedBy() default {};
+
+ /**
+ * String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
+ * only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
+ * for each of the specified qualifier names.
+ *
+ * Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
+ * are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
+ * number of qualifiers as no custom annotation types are needed.
+ *
+ * @return One or more qualifier name(s)
+ * @see #qualifiedBy()
+ * @see Named
+ */
+ String[] qualifiedByName() default {};
+}
diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java
new file mode 100644
index 0000000000..d6aac264d4
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SubclassMappings.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.mapstruct.util.Experimental;
+
+/**
+ * Configures the SubclassMappings of several subclasses.
+ *
+ * TIP: When using java 8 or later, you can omit the @SubclassMappings
+ * Wrapper annotation and directly specify several @SubclassMapping annotations
+ * on one method.
+ *
+ *
+ * @author Ben Zegveld
+ * @since 1.5
+ */
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Experimental
+public @interface SubclassMappings {
+
+ /**
+ * The subclassMappings that should be applied.
+ *
+ * @return the subclassMappings to apply.
+ */
+ SubclassMapping[] value();
+
+}
diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java
new file mode 100644
index 0000000000..c7ab8d957d
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation marks a presence check method parameter as a target property name parameter.
+ *
+ * This parameter enables conditional filtering based on target property name at run-time.
+ * Parameter must be of type {@link String} and can be present only in {@link Condition} method.
+ *
+ * @author Nikola Ivačič
+ * @since 1.6
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetPropertyName {
+}
diff --git a/core/src/main/java/org/mapstruct/ValueMapping.java b/core/src/main/java/org/mapstruct/ValueMapping.java
index 21ea5f4a83..7ad3726e48 100644
--- a/core/src/main/java/org/mapstruct/ValueMapping.java
+++ b/core/src/main/java/org/mapstruct/ValueMapping.java
@@ -84,7 +84,7 @@
*/
@Repeatable(ValueMappings.class)
@Retention(RetentionPolicy.CLASS)
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface ValueMapping {
/**
* The source value constant to use for this mapping.
diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java
index faefbf7105..95d4c7d5ce 100644
--- a/core/src/main/java/org/mapstruct/ValueMappings.java
+++ b/core/src/main/java/org/mapstruct/ValueMappings.java
@@ -40,10 +40,15 @@
*
* @author Sjaak Derksen
*/
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ValueMappings {
+ /**
+ * The value mappings that should be applied.
+ *
+ * @return the value mappings
+ */
ValueMapping[] value();
}
diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java
index c44797602a..12c95c4494 100644
--- a/core/src/main/java/org/mapstruct/control/MappingControl.java
+++ b/core/src/main/java/org/mapstruct/control/MappingControl.java
@@ -11,7 +11,6 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-
/**
* Controls which means of mapping are considered between the source and the target in mappings.
*
@@ -99,8 +98,16 @@
@MappingControl( MappingControl.Use.COMPLEX_MAPPING )
public @interface MappingControl {
+ /**
+ * The type of mapping control that should be used.
+ *
+ * @return What should be used for the mapping control
+ */
Use value();
+ /**
+ * Defines the options that can be used for the mapping control.
+ */
enum Use {
/**
diff --git a/core/src/main/java/org/mapstruct/control/MappingControls.java b/core/src/main/java/org/mapstruct/control/MappingControls.java
index a3efef661f..c1663e551e 100644
--- a/core/src/main/java/org/mapstruct/control/MappingControls.java
+++ b/core/src/main/java/org/mapstruct/control/MappingControls.java
@@ -21,5 +21,10 @@
@Target(ElementType.ANNOTATION_TYPE)
public @interface MappingControls {
+ /**
+ * The mapping controls that should be applied to the annotated class.
+ *
+ * @return The mapping controls that should be applied to the annotated class.
+ */
MappingControl[] value();
}
diff --git a/core/src/main/java/org/mapstruct/factory/Mappers.java b/core/src/main/java/org/mapstruct/factory/Mappers.java
index 05b616fe9a..d20bd59fa3 100644
--- a/core/src/main/java/org/mapstruct/factory/Mappers.java
+++ b/core/src/main/java/org/mapstruct/factory/Mappers.java
@@ -84,10 +84,10 @@ private static T doGetMapper(Class clazz, ClassLoader classLoader) throws
return constructor.newInstance();
}
- catch (ClassNotFoundException e) {
+ catch ( ClassNotFoundException e ) {
return getMapperFromServiceLoader( clazz, classLoader );
}
- catch ( InstantiationException | InvocationTargetException | IllegalAccessException e) {
+ catch ( InstantiationException | InvocationTargetException | IllegalAccessException e ) {
throw new RuntimeException( e );
}
}
diff --git a/core/src/main/java/org/mapstruct/package-info.java b/core/src/main/java/org/mapstruct/package-info.java
index 02f2b31a42..5a53b5a6d3 100644
--- a/core/src/main/java/org/mapstruct/package-info.java
+++ b/core/src/main/java/org/mapstruct/package-info.java
@@ -13,6 +13,6 @@
* This package contains several annotations which allow to configure how mapper interfaces are generated.
*
*
- * @see MapStruct reference documentation
+ * @see MapStruct reference documentation
*/
package org.mapstruct;
diff --git a/core/src/main/java/org/mapstruct/util/Experimental.java b/core/src/main/java/org/mapstruct/util/Experimental.java
index a945a87d7c..5fd5eb3b5c 100644
--- a/core/src/main/java/org/mapstruct/util/Experimental.java
+++ b/core/src/main/java/org/mapstruct/util/Experimental.java
@@ -17,5 +17,11 @@
@Documented
@Retention(RetentionPolicy.SOURCE)
public @interface Experimental {
+
+ /**
+ * The reason why the feature is considered experimental.
+ *
+ * @return the reason why the feature is considered experimental.
+ */
String value() default "";
}
diff --git a/core/src/test/java/org/mapstruct/factory/MappersTest.java b/core/src/test/java/org/mapstruct/factory/MappersTest.java
index 6118ba09cb..cde4b5be3b 100644
--- a/core/src/test/java/org/mapstruct/factory/MappersTest.java
+++ b/core/src/test/java/org/mapstruct/factory/MappersTest.java
@@ -5,12 +5,12 @@
*/
package org.mapstruct.factory;
-import static org.assertj.core.api.Assertions.assertThat;
-
import org.junit.jupiter.api.Test;
import org.mapstruct.test.model.Foo;
import org.mapstruct.test.model.SomeClass;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* Unit test for {@link Mappers}.
*
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 337ad35e51..b480e24a06 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
@@ -39,6 +39,18 @@
org.mapstruct.tools.gemgem-api
+
+
+ org.jetbrains.kotlin
+ kotlin-metadata-jvm
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ provided
+ true
+
@@ -59,7 +71,7 @@
org.freemarkerfreemarker${project.build.directory}/freemarker-unpacked
- META-INF/LICENSE.txt,META-INF/NOTICE.txt
+ META-INF/LICENSE
@@ -93,7 +105,7 @@
MapStruct ${project.version}MapStruct ${project.version}
- MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]>
+ MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]>
@@ -123,7 +135,7 @@
${basedir}/src/main/assembly/dist.xml
mapstruct-${project.version}
- gnu
+ posix
@@ -187,7 +199,6 @@
javax.xml.bindjaxb-api
- 2.3.1providedtrue
diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml
index f519e5fc9c..e0bc00496b 100644
--- a/distribution/src/main/assembly/dist.xml
+++ b/distribution/src/main/assembly/dist.xml
@@ -42,11 +42,7 @@
/
- target/freemarker-unpacked/META-INF/NOTICE.txt
- /
-
-
- target/freemarker-unpacked/META-INF/LICENSE.txt
+ target/freemarker-unpacked/META-INF/LICENSEetc/freemarker
@@ -80,7 +76,7 @@
- target/site/apidocs
+ target/reports/apidocsdocs/api
diff --git a/documentation/pom.xml b/documentation/pom.xml
index 8850da641c..21a2b151bd 100644
--- a/documentation/pom.xml
+++ b/documentation/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
index d3ab2424b1..306169193d 100644
--- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
@@ -88,7 +88,7 @@ Default expressions are a combination of default values and expressions. They wi
The same warnings and restrictions apply to default expressions that apply to expressions. Only Java is supported, and MapStruct will not validate the expression at generation-time.
-The example below demonstrates how two source properties can be mapped to one target:
+The example below demonstrates how a default expression can be used to set a value when the source attribute is not present (e.g. is `null`):
.Mapping method using a default expression
====
@@ -110,6 +110,51 @@ public interface SourceTargetMapper {
The example demonstrates how to use defaultExpression to set an `ID` field if the source field is null, this could be used to take the existing `sourceId` from the source object if it is set, or create a new `Id` if it isn't. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `UUID` class (unless it’s used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining imports on the @Mapper annotation (see <>).
+[[sub-class-mappings]]
+=== Subclass Mapping
+
+When both input and result types have an inheritance relation, you would want the correct specialization be mapped to the matching specialization.
+Suppose an `Apple` and a `Banana`, which are both specializations of `Fruit`.
+
+.Specifying the sub class mappings of a fruit mapping
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface FruitMapper {
+
+ @SubclassMapping( source = AppleDto.class, target = Apple.class )
+ @SubclassMapping( source = BananaDto.class, target = Banana.class )
+ Fruit map( FruitDto source );
+
+}
+----
+====
+
+If you would just use a normal mapping both the `AppleDto` and the `BananaDto` would be made into a `Fruit` object, instead of an `Apple` and a `Banana` object.
+By using the subclass mapping an `AppleDtoToApple` mapping will be used for `AppleDto` objects, and an `BananaDtoToBanana` mapping will be used for `BananaDto` objects.
+If you try to map a `GrapeDto` it would still turn it into a `Fruit`.
+
+In the case that the `Fruit` is an abstract class or an interface, you would get a compile error.
+
+To allow mappings for abstract classes or interfaces you need to set the `subclassExhaustiveStrategy` to `RUNTIME_EXCEPTION`, you can do this at the `@MapperConfig`, `@Mapper` or `@BeanMapping` annotations. If you then pass a `GrapeDto` an `IllegalArgumentException` will be thrown because it is unknown how to map a `GrapeDto`.
+Adding the missing (`@SubclassMapping`) for it will fix that.
+
+<> can be used to further control which methods may be chosen to map a specific subclass. For that, you will need to use one of `SubclassMapping#qualifiedByName` or `SubclassMapping#qualifiedBy`.
+
+[TIP]
+====
+If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings.
+====
+
+[NOTE]
+====
+Combining `@SubclassMapping` with update methods is not supported.
+If you try to use subclass mappings there will be a compile error.
+The same issue exists for the `@Context` and `@TargetType` parameters.
+====
+
[[determining-result-type]]
=== Determining the result type
@@ -159,7 +204,8 @@ The mechanism is also present on iterable mapping and map mapping. `@IterableMap
[[mapping-result-for-null-arguments]]
=== Controlling mapping result for 'null' arguments
-MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned.
+MapStruct offers control over the object to create when the source argument of the mapping method equals `null`.
+By default `null` will be returned, or `Optional.empty()` if the return type is `Optional`.
However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MapperConfig`, the mapping result can be altered to return empty *default* values. This means for:
@@ -169,13 +215,36 @@ However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETU
The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MapperConfig#nullValueMappingStrategy`.
+[[mapping-result-for-null-collection-or-map-arguments]]
+=== Controlling mapping result for 'null' collection or map arguments
+
+With <> it is possible to control how the return type should be constructed when the source argument of the mapping method is `null`.
+That is applied for all mapping methods (bean, iterable or map mapping methods).
+
+However, MapStruct also offers a more dedicated way to control how collections / maps should be mapped.
+e.g. return default (empty) collections / maps, but return `null` for beans.
+
+For collections (iterables) this can be controlled through:
+
+* `MapperConfig#nullValueIterableMappingStrategy`
+* `Mapper#nullValueIterableMappingStrategy`
+* `IterableMapping#nullValueMappingStrategy`
+
+For maps this can be controlled through:
+
+* `MapperConfig#nullValueMapMappingStrategy`
+* `Mapper#nullValueMapMappingStrategy`
+* `MapMapping#nullValueMappingStrategy`
+
+How the value of the `NullValueMappingStrategy` is applied is the same as in <>
+
[[mapping-result-for-null-properties]]
=== Controlling mapping result for 'null' properties in bean mappings (update mapping methods only).
MapStruct offers control over the property to set in an `@MappingTarget` annotated target bean when the source property equals `null` or the presence check method results in 'absent'.
-By default the target property will be set to null.
+By default the target property will be set to `null`, or `Optional.empty()` if the target property is `Optional`.
However:
@@ -185,6 +254,9 @@ For all other objects an new instance is created. Please note that a default con
2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target.
+3. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the target collection or map will be cleared when the source property is `null`.
+This strategy only applies to `Collection` and `Map` target properties.
+
The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MapperConfig#nullValuePropertyMappingStrategy`.
[NOTE]
@@ -198,6 +270,18 @@ null check, regardless of the value of the `NullValuePropertyMappingStrategy`, t
`NullValuePropertyMappingStrategy` also applies when the presence checker returns `not present`.
====
+[NOTE]
+====
+When working with `java.util.Optional` types, `NullValuePropertyMappingStrategy` applies to empty Optionals in the same way it applies to null values.
+An empty `Optional` (i.e., `Optional.isEmpty()` returns `true`) is treated similarly to a `null` value for the purposes of this strategy. For example:
+
+* With `IGNORE` strategy: an empty `Optional` source property will not update the target property
+* With `SET_TO_NULL` strategy: an empty `Optional` source will set the target to `null` (for non-`Optional` targets) or `Optional.empty()` (for Optional targets)
+* With `SET_TO_DEFAULT` strategy: an empty `Optional` source will set the target to its default value
+
+See <> for detailed examples and behavior with Optional types.
+====
+
[[checking-source-property-for-null-arguments]]
=== Controlling checking result for 'null' properties in bean mapping
@@ -213,7 +297,50 @@ First calling a mapping method on the source property is not protected by a null
The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean.
-The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MaperConfig#nullValueCheckStrategy`.
+The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MapperConfig#nullValueCheckStrategy`.
+
+[[jspecify-nullness-annotations]]
+=== JSpecify nullness annotations
+
+MapStruct recognises https://jspecify.dev/[JSpecify] nullness annotations on source and target types and uses them to refine when null checks are generated. Only the annotations from `org.jspecify.annotations` are interpreted; MapStruct does not introduce a compile-time dependency on JSpecify, so consumers opt in by adding the dependency themselves.
+
+The following annotations participate:
+
+* `@NonNull`: the annotated value is guaranteed not to be `null`.
+* `@Nullable`: the annotated value may be `null`.
+* `@NullMarked`: within this scope (package, class, or method), unannotated reference types are treated as `@NonNull`.
+* `@NullUnmarked`: reverts to unknown nullability within its scope (overrides a `@NullMarked` outer scope).
+
+==== Property-level rules
+
+For a single property mapping:
+
+* If the source is `@NonNull`, the null check is skipped, since the value is treated as guaranteed non-null.
+* If the target is `@NonNull` (and the source is not `@NonNull`), a null check is always added so the target's contract is not violated.
+* In all other cases, the existing `NullValueCheckStrategy` decides.
+
+The existing safety guards (`defaultValue` / `defaultExpression`, primitive unboxing, and the relevant `NullValuePropertyMappingStrategy` modes) continue to apply.
+
+==== Scope: `@NullMarked` and `@NullUnmarked`
+
+`@NullMarked` and `@NullUnmarked` are resolved by walking from the annotated element outward: method, then declaring class, then outer classes, then package. The closest annotation wins: a `@NullUnmarked` method inside a `@NullMarked` class reverts only that method, while a `@Nullable` annotation on an individual type always overrides the surrounding scope.
+
+==== Method-level source parameter
+
+When the source parameter of a mapping method is annotated `@NonNull` (directly or via a `@NullMarked` scope), MapStruct skips the method-level null guard, since the caller is contractually obliged to pass a non-null value.
+This rule applies uniformly to bean, iterable, map, and stream mapping methods.
+
+==== Method-level return type
+
+When the return type of a mapping method is `@NonNull` (directly or via a `@NullMarked` scope), MapStruct forces `NullValueMappingStrategy.RETURN_DEFAULT` semantics. The generated method returns a default-constructed target (bean methods), an empty collection (`Iterable` / array mappings), an empty map (`Map` mappings), or `Stream.empty()` (stream mappings) rather than `null`, so the return contract is never violated. This rule applies regardless of the explicit `NullValueMappingStrategy` setting.
+
+==== Constructor parameter constraint
+
+If a property mapping would assign a potentially `null` source value to a `@NonNull` constructor parameter, MapStruct raises a *compilation error*. Neither inserting a null check (which would leave the variable at `null` and violate the contract) nor passing the value through is safe. Provide a `defaultValue` or `defaultExpression` on the `@Mapping` to satisfy the parameter when the source is absent.
+
+==== Disabling JSpecify support
+
+All JSpecify-driven inference can be turned off by setting the processor option `-Amapstruct.disableJSpecify=true`. With the option enabled, MapStruct ignores every JSpecify annotation (`@NonNull`, `@Nullable`, `@NullMarked`, `@NullUnmarked`) and the compilation error for nullable sources mapped to `@NonNull` constructor parameters is not raised. Null-check generation falls back to the pre-JSpecify behavior driven by `NullValueCheckStrategy`. Use this flag when you need to freeze MapStruct's null-handling behavior while JSpecify annotations exist elsewhere on the classpath.
[[source-presence-check]]
=== Source presence checking
@@ -226,7 +353,7 @@ The source presence checker name can be changed in the MapStruct service provide
[NOTE]
====
-Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor see `CollectionMappingStrategy`, MapStruct will always generate a source property
+Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (see `CollectionMappingStrategy`), MapStruct will always generate a source property
null check, regardless the value of the `NullValueCheckStrategy` to avoid addition of `null` to the target collection or map.
====
@@ -235,10 +362,12 @@ null check, regardless the value of the `NullValueCheckStrategy` to avoid additi
Conditional Mapping is a type of <>.
The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not.
+Conditional mapping can also be used to check if a source parameter should be mapped or not.
-A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
+A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
+A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)`
-e.g. if you only want to map a String property when it is not `null, and it is not empty then you can do something like:
+e.g. if you only want to map a String property when it is not `null` and not empty then you can do something like:
.Mapper using custom condition check method
====
@@ -260,7 +389,7 @@ public interface CarMapper {
The generated mapper will look like:
-.try-catch block in generated implementation
+.Custom condition check in generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
@@ -288,6 +417,119 @@ public class CarMapperImpl implements CarMapper {
----
====
+When using this in combination with an update mapping method it will replace the `null-check` there, for example:
+
+.Update mapper using custom condition check method
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface CarMapper {
+
+ CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
+
+ @Condition
+ default boolean isNotEmpty(String value) {
+ return value != null && !value.isEmpty();
+ }
+}
+----
+====
+
+The generated update mapper will look like:
+
+.Custom condition check in generated implementation
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class CarMapperImpl implements CarMapper {
+
+ @Override
+ public CarDto carToCarDto(Car car, CarDto carDto) {
+ if ( car == null ) {
+ return carDto;
+ }
+
+ if ( isNotEmpty( car.getOwner() ) ) {
+ carDto.setOwner( car.getOwner() );
+ } else {
+ carDto.setOwner( null );
+ }
+
+ // Mapping of other properties
+
+ return carDto;
+ }
+}
+----
+====
+
+Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method:
+
+.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface CarMapper {
+
+ @Mapping(target = "owner", source = "ownerName")
+ CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
+
+ @Condition
+ default boolean isNotEmpty(
+ String value,
+ @TargetPropertyName String targetPropertyName,
+ @SourcePropertyName String sourcePropertyName
+ ) {
+
+ if ( targetPropertyName.equals( "owner" )
+ && sourcePropertyName.equals( "ownerName" ) ) {
+
+ return value != null
+ && !value.isEmpty()
+ && !value.equals( value.toLowerCase() );
+ }
+ return value != null && !value.isEmpty();
+ }
+}
+----
+====
+
+The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like:
+
+.Custom condition check in generated implementation
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class CarMapperImpl implements CarMapper {
+
+ @Override
+ public CarDto carToCarDto(Car car, CarDto carDto) {
+ if ( car == null ) {
+ return carDto;
+ }
+
+ if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) {
+ carDto.setOwner( car.getOwner() );
+ } else {
+ carDto.setOwner( null );
+ }
+
+ // Mapping of other properties
+
+ return carDto;
+ }
+}
+----
+====
+
[IMPORTANT]
====
If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself.
@@ -296,11 +538,62 @@ If there is a custom `@Condition` method applicable for the property it will hav
[NOTE]
====
Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input.
+
+`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods.
====
<> is also valid for `@Condition` methods.
In order to use a more specific condition method you will need to use one of `Mapping#conditionQualifiedByName` or `Mapping#conditionQualifiedBy`.
+If we want to only map cars that have an id provided then we can do something like:
+
+
+.Mapper using custom condition source parameter check method
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface CarMapper {
+
+ CarDto carToCarDto(Car car);
+
+ @SourceParameterCondition
+ default boolean hasCar(Car car) {
+ return car != null && car.getId() != null;
+ }
+}
+----
+====
+
+The generated mapper will look like:
+
+.Custom condition source parameter check generated implementation
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class CarMapperImpl implements CarMapper {
+
+ @Override
+ public CarDto carToCarDto(Car car) {
+ if ( !hasCar( car ) ) {
+ return null;
+ }
+
+ CarDto carDto = new CarDto();
+
+ carDto.setOwner( car.getOwner() );
+
+ // Mapping of other properties
+
+ return carDto;
+ }
+}
+----
+====
+
[[exceptions]]
=== Exceptions
diff --git a/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc
index fb8c71a622..efacaedbe5 100644
--- a/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc
@@ -46,6 +46,8 @@ In case of bi-directional mappings, e.g. from entity to DTO and from DTO to enti
Use the annotation `@InheritInverseConfiguration` to indicate that a method shall inherit the inverse configuration of the corresponding reverse method.
+In the example below, there is no need to write the inverse mapping manually. Think of a case where there are several mappings, so writing the inverse ones can be cumbersome and error prone.
+
.Inverse mapping method inheriting its configuration and ignoring some of them
====
[source, java, linenums]
@@ -162,4 +164,4 @@ The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingIn
* `EXPLICIT` (default): the configuration will only be inherited, if the target mapping method is annotated with `@InheritConfiguration` and the source and target types are assignable to the corresponding types of the prototype method, all as described in <>.
* `AUTO_INHERIT_FROM_CONFIG`: the configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritConfiguration(name = ...)` which will cause `AUTO_INHERIT_FROM_CONFIG` to be ignored.
* `AUTO_INHERIT_REVERSE_FROM_CONFIG`: the inverse configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritInverseConfiguration(name = ...)` which will cause ``AUTO_INHERIT_REVERSE_FROM_CONFIG` to be ignored.
-* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`.
\ No newline at end of file
+* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`.
diff --git a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
index 0c873eac09..dc07b30e62 100644
--- a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc
@@ -248,9 +248,8 @@ All before/after-mapping methods that *can* be applied to a mapping method *will
The order of the method invocation is determined primarily by their variant:
-1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source
-parameters and constructing a new target bean.
-2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean.
+1. `@BeforeMapping` methods without parameters, a `@MappingTarget` parameter or a `@TargetType` parameter are called before any null-checks on source parameters and constructing a new target bean.
+2. `@BeforeMapping` methods with a `@MappingTarget` parameter are called after constructing a new target bean.
3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement.
Within those groups, the method invocations are ordered by their location of definition:
@@ -262,4 +261,11 @@ Within those groups, the method invocations are ordered by their location of def
*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation.
-*Important:* when using a builder, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter so that the method is able to modify the object going to be build. The `build` method is called when the `@AfterMapping` annotated method scope finishes. MapStruct will not call the `@AfterMapping` annotated method if the real target is used as `@MappingTarget` annotated parameter.
\ No newline at end of file
+[NOTE]
+====
+Before/After-mapping methods can also be used with builders:
+
+* `@BeforeMapping` methods with a `@MappingTarget` parameter of the real target will not be invoked because it is only available after the mapping was already performed.
+* To be able to modify the object that is going to be built, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter. The `build` method is called when the `@AfterMapping` annotated method scope finishes.
+* The `@AfterMapping` annotated method can also have the real target as `@TargetType` or `@MappingTarget`. It will be invoked after the real target was built (first the methods annotated with `@TargetType`, then the methods annotated with `@MappingTarget`)
+====
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
index 51fec968bf..51b2bbff3c 100644
--- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
@@ -1,7 +1,18 @@
[[using-spi]]
== Using the MapStruct SPI
+
+To use a custom SPI implementation, it must be located in a separate JAR file together with a file named after the SPI (e.g. `org.mapstruct.ap.spi.AccessorNamingStrategy`) in `META-INF/services/` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
+
+
+[NOTE]
+====
+It might also be necessary to add the jar to your IDE's annotation processor factory path. Otherwise you might get an error stating that it cannot be found, while a run using your build tool does succeed.
+====
+
=== Custom Accessor Naming Strategy
+SPI name: `org.mapstruct.ap.spi.AccessorNamingStrategy`
+
MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provider Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below.
.Source object GolfPlayer with fluent API.
@@ -60,7 +71,7 @@ public class GolfPlayerDto {
public GolfPlayerDto withName(String name) {
this.name = name;
- return this
+ return this;
}
}
----
@@ -121,14 +132,14 @@ public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy
====
The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.
-To use a custom SPI implementation, it must be located in a separate JAR file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
-
[TIP]
Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples).
-[mapping-exclusion-provider]
+[[mapping-exclusion-provider]]
=== Mapping Exclusion Provider
+SPI name: `org.mapstruct.ap.spi.MappingExclusionProvider`
+
MapStruct offers the possibility to override the `MappingExclusionProvider` via the Service Provider Interface (SPI).
A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type,
i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type.
@@ -177,16 +188,12 @@ include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusio
----
====
-To use a custom SPI implementation, it must be located in a separate JAR file
-together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider` with the fully qualified name of your custom implementation as content
-(e.g. `org.mapstruct.example.CustomMappingExclusionProvider`).
-This JAR file needs to be added to the annotation processor classpath
-(i.e. add it next to the place where you added the mapstruct-processor jar).
-
[[custom-builder-provider]]
=== Custom Builder Provider
+SPI name: org.mapstruct.ap.spi.BuilderProvider
+
MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom builder strategy.
@@ -202,6 +209,8 @@ include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
[[custom-enum-naming-strategy]]
=== Custom Enum Naming Strategy
+SPI name: `org.mapstruct.ap.spi.EnumMappingStrategy`
+
MapStruct offers the possibility to override the `EnumMappingStrategy` via the Service Provider Interface (SPI).
This can be used when you have certain enums that follow some conventions within your organization.
For example all enums which implement an interface named `CustomEnumMarker` are prefixed with `CUSTOM_`
@@ -349,6 +358,8 @@ public class CheeseTypeMapperImpl implements CheeseTypeMapper {
[[custom-enum-transformation-strategy]]
=== Custom Enum Transformation Strategy
+SPI name: `org.mapstruct.ap.spi.EnumTransformationStrategy`
+
MapStruct offers the possibility to other transformations strategies by implementing `EnumTransformationStrategy` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom transformation strategy.
@@ -359,4 +370,55 @@ A nice example is to provide support for a custom transformation strategy.
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
+====
+
+[[additional-supported-options-provider]]
+=== Additional Supported Options Provider
+
+SPI name: `org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider`
+
+MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessor
+to the individual SPIs, by implementing `AdditionalSupportedOptionsProvider` via the Service Provider Interface (SPI).
+
+.Custom Additional Supported Options Provider that declares `myorg.custom.defaultNullEnumConstant` as an option to pass through
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java[tag=documentation]
+----
+====
+
+The value of this option is provided by including an `arg` to the `compilerArgs` tag when defining your custom SPI
+implementation.
+
+.Example maven configuration with additional options
+====
+[source, maven, linenums]
+[subs="verbatim,attributes"]
+----
+
+
+
+ org.myorg
+ custom-spi-impl
+ ${project.version}
+
+
+
+ -Amyorg.custom.defaultNullEnumConstant=MISSING
+
+
+----
+====
+
+Your custom SPI implementations can then access this configured value via `MapStructProcessingEnvironment#getOptions()`.
+
+.Accessing your custom options
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+include::{processor-ap-test}/additionalsupportedoptions/UnknownEnumMappingStrategy.java[tag=documentation]
+----
====
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc
index c31424a0de..ef2a58f82e 100644
--- a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc
@@ -46,8 +46,10 @@ public @interface Default {
MapStruct works together with https://projectlombok.org/[Project Lombok] as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14.
MapStruct takes advantage of generated getters, setters, and constructors and uses them to generate the mapper implementations.
+Be reminded that the generated code by Lombok might not always be compatible with the expectations from the individual mappings.
+In such a case, either Mapstruct mapping must be changed or Lombok must be configured accordingly using https://projectlombok.org/features/configuration[`lombok.config`] for mutual synergy.
-[NOTE]
+[WARNING]
====
Lombok 1.18.16 introduces a breaking change (https://projectlombok.org/changelog[changelog]).
The additional annotation processor `lombok-mapstruct-binding` (https://mvnrepository.com/artifact/org.projectlombok/lombok-mapstruct-binding[Maven]) must be added otherwise MapStruct stops working with Lombok.
@@ -58,7 +60,7 @@ This resolves the compilation issues of Lombok and MapStruct modules.
org.projectlomboklombok-mapstruct-binding
- 0.1.0
+ 0.2.0
----
====
@@ -121,7 +123,7 @@ The set up using Maven or Gradle does not differ from what is described in <org.projectlomboklombok-mapstruct-binding
- 0.1.0
+ 0.2.0
@@ -140,8 +142,8 @@ The set up using Maven or Gradle does not differ from what is described in <org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.1
+ 3.14.1
- 1.8
- 1.8
+ ${java.version}
+ ${java.version}org.mapstruct
@@ -130,7 +130,7 @@ You can find a complete example in the https://github.com/mapstruct/mapstruct-ex
The MapStruct code generator can be configured using _annotation processor options_.
-When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using an `options` element within the configuration of the Maven processor plug-in like this:
+When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using `compilerArgs` within the configuration of the Maven processor plug-in like this:
.Maven configuration
====
@@ -141,10 +141,10 @@ When invoking javac directly, these options are passed to the compiler in the fo
org.apache.maven.pluginsmaven-compiler-plugin
- 3.5.1
+ 3.14.1
- 1.8
- 1.8
+ ${java.version}
+ ${java.version}org.mapstruct
@@ -215,9 +215,11 @@ suppressGeneratorVersionInfoComment`
Supported values are:
* `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)`
-* `cdi`: the generated mapper is an application-scoped CDI bean and can be retrieved via `@Inject`
+* `cdi`: the generated mapper is an application-scoped (from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority) CDI bean and can be retrieved via `@Inject`
* `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired`
-* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject`, e.g. using Spring
+* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring
+* `jakarta`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from jakarta.inject), e.g. using Spring
+* `jakarta-cdi`: the generated mapper is an application-scoped (from jakarta.enterprise.context) CDI bean and can be retrieved via `@Inject`
If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence.
|`default`
@@ -247,6 +249,53 @@ Supported values are:
If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence.
If a policy is given for a specific bean mapping via `@BeanMapping#unmappedTargetPolicy()`, it takes precedence over both `@Mapper#unmappedTargetPolicy()` and the option.
|`WARN`
+
+|`mapstruct.unmappedSourcePolicy`
+|The default reporting policy to be applied in case an attribute of the source object of a mapping method is not populated with a target value.
+
+Supported values are:
+
+* `ERROR`: any unmapped source property will cause the mapping code generation to fail
+* `WARN`: any unmapped source property will cause a warning at build time
+* `IGNORE`: unmapped source properties are ignored
+
+If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence.
+If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option.
+|`IGNORE`
+
+|`mapstruct.
+disableBuilders`
+|If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers.
+|`false`
+
+|`mapstruct.
+disableJSpecify`
+|If set to `true`, MapStruct ignores all JSpecify nullness annotations (`@NonNull`, `@Nullable`, `@NullMarked`, `@NullUnmarked`) when deciding whether to generate null checks and does not raise the compilation error for a nullable source mapped to a `@NonNull` constructor parameter. Null-check generation falls back to the `NullValueCheckStrategy`-driven behavior.
+|`false`
+
+|`mapstruct.nullValueIterableMappingStrategy`
+|The strategy to be applied when `null` is passed as a source value to an iterable mapping.
+
+Supported values are:
+
+* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
+* `RETURN_DEFAULT`: if `null` is passed then a default value (empty collection) will be returned
+
+If a strategy is given for a specific mapper via `@Mapper#nullValueIterableMappingStrategy()`, the value from the annotation takes precedence.
+If a strategy is given for a specific iterable mapping via `@IterableMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueIterableMappingStrategy()` and the option.
+|`RETURN_NULL`
+
+|`mapstruct.nullValueMapMappingStrategy`
+|The strategy to be applied when `null` is passed as a source value to a map mapping.
+
+Supported values are:
+
+* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
+* `RETURN_DEFAULT`: if `null` is passed then a default value (empty map) will be returned
+
+If a strategy is given for a specific mapper via `@Mapper#nullValueMapMappingStrategy()`, the value from the annotation takes precedence.
+If a strategy is given for a specific map mapping via `@MapMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueMapMappingStrategy()` and the option.
+|`RETURN_NULL`
|===
=== Using MapStruct with the Java Module System
@@ -254,3 +303,87 @@ If a policy is given for a specific bean mapping via `@BeanMapping#unmappedTarge
MapStruct can be used with Java 9 and higher versions.
To allow usage of the `@Generated` annotation `java.annotation.processing.Generated` (part of the `java.compiler` module) can be enabled.
+
+=== IDE Integration
+
+There are optional MapStruct plugins for IntelliJ and Eclipse that allow you to have additional completion support (and more) in the annotations.
+
+==== IntelliJ
+
+The https://plugins.jetbrains.com/plugin/10036-mapstruct-support[MapStruct IntelliJ] plugin offers assistance in projects that use MapStruct.
+
+Some features include:
+
+* Code completion in `target`, `source`, `expression`
+* Go To Declaration for properties in `target` and `source`
+* Find Usages of properties in `target` and `source`
+* Refactoring support
+* Errors and Quick Fixes
+
+==== Eclipse
+
+The https://marketplace.eclipse.org/content/mapstruct-eclipse-plugin[MapStruct Eclipse] Plugin offers assistance in projects that use MapStruct.
+
+Some features include:
+
+* Code completion in `target` and `source`
+* Quick Fixes
+
+[[kotlin-setup]]
+=== Kotlin Support
+
+MapStruct provides support for Kotlin interoperability with Java.
+
+When using MapStruct with Kotlin, it's recommended to add the `org.jetbrains.kotlin:kotlin-metadata-jvm` library to enable proper introspection of Kotlin metadata:
+
+.Maven configuration for Kotlin support
+====
+[source, xml, linenums]
+[subs="verbatim,attributes"]
+----
+...
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.1
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-metadata-jvm
+ ${kotlin.version}
+
+
+
+
+
+
+----
+====
+
+.Gradle configuration for Kotlin support
+====
+[source, groovy, linenums]
+[subs="verbatim,attributes"]
+----
+dependencies {
+ implementation "org.mapstruct:mapstruct:${mapstructVersion}"
+
+ annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
+ annotationProcessor "org.jetbrains.kotlin:kotlin-metadata-jvm:${kotlinVersion}"
+}
+----
+====
+
+[NOTE]
+====
+The `org.jetbrains.kotlin:kotlin-metadata-jvm` dependency is optional but highly recommended when working with Kotlin and Java.
+Without it, MapStruct will still work but may not handle complex Kotlin scenarios optimally.
+====
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
index 2d2b930114..96c76fc20d 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -39,7 +39,8 @@ The property name as defined in the http://www.oracle.com/technetwork/java/javas
====
[TIP]
====
-By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties.
+By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings (including nested ones) have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties.
+This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`.
====
[TIP]
====
@@ -125,7 +126,7 @@ Collection-typed attributes with the same element type will be copied by creatin
MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.
[[mapping-composition]]
-=== Mapping Composition (experimental)
+=== Mapping Composition
MapStruct supports the use of meta annotations. The `@Mapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. This allows `@Mapping` to be used on other (user defined) annotations for re-use purposes. For example:
====
@@ -163,7 +164,7 @@ public interface StorageMapper {
Still, they do have some properties in common. The `@ToEntity` assumes both target beans `ShelveEntity` and `BoxEntity` have properties: `"id"`, `"creationDate"` and `"name"`. It furthermore assumes that the source beans `ShelveDto` and `BoxDto` always have a property `"groupName"`. This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck.
-This feature is still experimental. Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
+Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
Therefore, the user should use this feature with care, especially when uncertain when a property is always present.
A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use `@InheritConfiguration` to achieve the same result (see <>).
@@ -301,7 +302,7 @@ If you don't want explicitly name all properties from nested source bean, you ca
The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
The same goes for `Customer.account`.
-When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
+When there are conflicts, these can be resolved by explicitly defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`).
@@ -420,8 +421,11 @@ If a Builder exists for a certain type, then that builder will be used for the m
The default implementation of the `BuilderProvider` assumes the following:
-* The type has a parameterless public static builder creation method that returns a builder.
-So for example `Person` has a public static method that returns `PersonBuilder`.
+* The type has either
+** A parameterless public static builder creation method that returns a builder.
+e.g. `Person` has a public static method that returns `PersonBuilder`.
+** A public static inner class with the name having the suffix "Builder", and a public no-args constructor
+e.g. `Person` has an inner class `PersonBuilder` with a public no-args constructor.
* The builder type has a parameterless public method (build method) that returns the type being built.
In our example `PersonBuilder` has a method returning `Person`.
* In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists
@@ -445,6 +449,11 @@ The <> are also considered for the builder type.
E.g. If an object factory exists for our `PersonBuilder` then this factory would be used instead of the builder creation method.
======
+[NOTE]
+======
+Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See <> for more information.
+======
+
.Person with Builder example
====
[source, java, linenums]
@@ -527,7 +536,7 @@ Otherwise, you would need to write a custom `BuilderProvider`
[TIP]
====
-In case you want to disable using builders then you can use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content.
+In case you want to disable using builders then you can pass the MapStruct processor option `mapstruct.disableBuilders` to the compiler. e.g. `-Amapstruct.disableBuilders=true`.
====
[[mapping-with-constructors]]
@@ -651,6 +660,421 @@ public class PersonMapperImpl implements PersonMapper {
----
====
+[[mapping-with-optional]]
+=== Using Optional
+
+MapStruct provides comprehensive support for Java's `Optional` type, allowing you to use `Optional` as both source and target types in your mappings.
+MapStruct handles all common `Optional` mapping scenarios, including conversions, nested properties, update mappings, and integration with builders and constructors.
+
+[[optional-basic-behavior]]
+==== Basic Optional Behavior
+
+When using `Optional` as a source type, MapStruct performs presence checks using `Optional.isEmpty()` / `!Optional.isPresent()` instead of `null` checks.
+When using `Optional` as a target type, MapStruct returns `Optional.empty()` instead of `null` when the source is absent.
+
+[[optional-mapping-scenarios]]
+==== Optional Mapping Scenarios
+
+MapStruct supports three primary mapping scenarios with `Optional`:
+
+1. `Optional` to `Optional` - Both source and target are wrapped in `Optional`
+2. `Optional` to Non-`Optional` - Source is `Optional`, target is a regular type
+3. Non-`Optional` to `Optional` - Source is a regular type, target is `Optional`
+
+.Example mapper showing all Optional scenarios
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface ProductMapper {
+
+ Optional map(Optional product);
+
+ ProductDto unwrap(Optional product);
+
+ Optional wrap(Product product);
+}
+----
+====
+
+.Generated code for `Optional` to `Optional`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+@Override
+public Optional map(Optional product) {
+ if ( product.isEmpty() ) {
+ return Optional.empty();
+ }
+
+ Product productValue = product.get();
+
+ ProductDto productDto = new ProductDto();
+
+ productDto.setName( productValue.getName() );
+ productDto.setPrice( productValue.getPrice() );
+
+ return Optional.of( productDto );
+}
+----
+====
+
+.Generated code for `Optional` to Non-`Optional`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+@Override
+public ProductDto unwrap(Optional product) {
+ if ( product.isEmpty() ) {
+ return null;
+ }
+
+ Product productValue = product.get();
+
+ ProductDto productDto = new ProductDto();
+
+ productDto.setName( productValue.getName() );
+ productDto.setPrice( productValue.getPrice() );
+
+ return productDto;
+}
+----
+====
+
+.Generated code for Non-`Optional` to `Optional`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+@Override
+public Optional wrap(Product product) {
+ if ( product == null ) {
+ return Optional.empty();
+ }
+
+ ProductDto productDto = new ProductDto();
+
+ productDto.setName( product.getName() );
+ productDto.setPrice( product.getPrice() );
+
+ return Optional.of( productDto );
+}
+----
+====
+
+[[optional-properties]]
+==== Mapping Optional Properties
+
+MapStruct handles `Optional` properties within your beans seamlessly.
+When mapping `Optional` properties, MapStruct generates appropriate presence checks before accessing the values.
+
+.Example with `Optional` properties
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+public class Car {
+ private Optional make;
+ private Optional numberOfSeats;
+ private Person driver;
+
+ // getters omitted for brevity
+}
+
+public class CarDto {
+ private String manufacturer;
+ private int seatCount;
+ private PersonDto driver;
+
+ // getters and setters omitted for brevity
+}
+
+@Mapper
+public interface CarMapper {
+
+ @Mapping(target = "manufacturer", source = "make")
+ @Mapping(target = "seatCount", source = "numberOfSeats")
+ CarDto carToCarDto(Car car);
+}
+----
+====
+
+.Code generated by MapStruct for `Optional` properties
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class CarMapperImpl implements CarMapper {
+
+ @Override
+ public CarDto carToCarDto(Car car) {
+ if ( car == null ) {
+ return null;
+ }
+
+ CarDto carDto = new CarDto();
+
+ if ( car.getMake().isPresent() ) {
+ carDto.setManufacturer( car.getMake().get() );
+ }
+ if ( car.getNumberOfSeats().isPresent() ) {
+ carDto.setSeatCount( car.getNumberOfSeats().get() );
+ }
+ carDto.setDriver( personToPersonDto( car.getDriver() ) );
+
+ return carDto;
+ }
+
+ protected PersonDto personToPersonDto(Person person) {
+ //...
+ }
+}
+----
+====
+
+[[optional-update-mappings]]
+==== Update Mappings with Optional
+
+`Optional` works seamlessly with update mappings using `@MappingTarget`.
+When the source `Optional` is present, the target object is updated with the unwrapped value.
+When the source `Optional` is empty, the behavior depends on the null value property mapping strategy.
+
+.Update mapping with an `Optional` source
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface ProductUpdateMapper {
+
+ void updateProduct(ProductUpdateDto dto, @MappingTarget Product product);
+}
+
+public class ProductUpdateDto {
+ private Optional name;
+ private Optional price;
+
+ // getters omitted
+}
+
+public class Product {
+ private String name;
+ private BigDecimal price;
+
+ // getters and setters omitted
+}
+----
+====
+
+.Generated update method
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+@Override
+public void updateProduct(ProductUpdateDto dto, Product product) {
+ if ( dto == null ) {
+ return;
+ }
+
+ if ( dto.getName().isPresent() ) {
+ product.setName( dto.getName().get() );
+ }
+ if ( dto.getPrice().isPresent() ) {
+ product.setPrice( dto.getPrice().get() );
+ }
+}
+----
+====
+
+[[optional-null-value-strategies]]
+==== Null Value Mapping Strategies
+
+When working with `Optional` types, MapStruct's null value mapping strategies behave as follows:
+
+* For `Optional` return types: `NullValueMappingStrategy.RETURN_DEFAULT` will return `Optional.empty()` instead of `null`
+* For `Optional` properties: When a source property `Optional` is empty, `NullValuePropertyMappingStrategy` determines whether to skip the property, set it to null/empty, or use a default value
+* `IGNORE` strategy: When a source `Optional` is empty, the target property is not updated
+* `SET_TO_NULL` strategy: When a source `Optional` is empty, the target property is set to `null` (for non-`Optional` targets) or `Optional.empty()` (for `Optional` targets)
+* `SET_TO_DEFAULT` strategy: When a source `Optional` is empty, the target property is set to its default value
+
+.Using null value strategies with `Optional`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+public interface ProductMapper {
+
+ void updateProduct(ProductUpdateDto dto, @MappingTarget Product product);
+}
+----
+====
+
+With the `IGNORE` strategy, when Optional properties are empty, they won't overwrite existing target values.
+
+[[optional-builders-constructors]]
+==== Optional with Builders and Constructors
+
+MapStruct's Optional support works seamlessly with both builder patterns and constructor-based mappings.
+
+.Optional with builders
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface PersonMapper {
+ Optional map(Optional person);
+}
+
+public class PersonDto {
+ private final String name;
+ private final Integer age;
+
+ private PersonDto(Builder builder) {
+ this.name = builder.name;
+ this.age = builder.age;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String name;
+ private Integer age;
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder age(Integer age) {
+ this.age = age;
+ return this;
+ }
+
+ public PersonDto build() {
+ return new PersonDto(this);
+ }
+ }
+
+ // getters omitted
+}
+----
+====
+
+.Generated code using builder
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+@Override
+public Optional map(Optional person) {
+ if ( person.isEmpty() ) {
+ return Optional.empty();
+ }
+
+ Person personValue = person.get();
+
+ PersonDto.Builder builder = PersonDto.builder();
+
+ builder.name( personValue.getName() );
+ builder.age( personValue.getAge() );
+
+ return Optional.of( builder.build() );
+}
+----
+====
+
+.Optional with constructors
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+public class PersonDto {
+ private final String name;
+ private final Integer age;
+
+ public PersonDto(String name, Integer age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ // getters omitted
+}
+----
+====
+
+When the target type uses constructors and has `Optional` properties, MapStruct will properly handle the `Optional` parameters.
+
+[[optional-complex-mappings]]
+==== Complex Optional Mappings
+
+MapStruct can handle complex scenarios where `Optional` is combined with nested object mappings, collections, and custom mapping methods.
+
+.Complex nested Optional mappings
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface OrderMapper {
+
+ OrderDto map(Order order);
+
+ // MapStruct will automatically use this for nested mappings
+ CustomerDto map(Customer customer);
+}
+
+public class Order {
+ private Optional customer;
+ private Optional shippingAddress;
+
+ // getters omitted
+}
+
+public class OrderDto {
+ private Optional customer;
+ private AddressDto shippingAddress;
+
+ // getters and setters omitted
+}
+----
+====
+
+When mapping nested objects that are wrapped in Optional, MapStruct will:
+
+1. Check if the source `Optional` is present
+2. Extract the value from the `Optional`
+3. Map the extracted value to the target type
+4. Wrap the result in an `Optional` if the target is also Optional
+
+[[optional-best-practices]]
+==== Best Practices and Limitations
+
+[NOTE]
+====
+While MapStruct fully supports `Optional` for target properties (public fields or setter parameters), the Java community generally recommends using `Optional` only for return types (source properties / getters).
+Consider your team's coding standards when deciding whether to use `Optional` for target properties.
+====
+
+[WARNING]
+====
+Avoid using `null` when returning `Optional` values to prevent `NullPointerException`.
+MapStruct assumes that if you're using `Optional`, the `Optional` itself is not null.
+====
+
[[mapping-map-to-bean]]
=== Mapping Map to Bean
@@ -696,7 +1120,7 @@ public class CustomerMapperImpl implements CustomerMapper {
customer.setId( Integer.parseInt( map.get( "id" ) ) );
}
if ( map.containsKey( "customerName" ) ) {
- customer.setName( source.get( "customerName" ) );
+ customer.setName( map.get( "customerName" ) );
}
// ...
}
@@ -715,3 +1139,143 @@ i.e. You can map from `Map` where for each property a conversio
When a raw map or a map that does not have a String as a key is used, then a warning will be generated.
The warning is not generated if the map itself is mapped into some other target property directly as is.
====
+
+[[adding-annotations]]
+=== Adding annotations
+
+Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers.
+Using the `@AnnotateWith` annotation you can generate an annotation at the specified location.
+
+For example Apache Camel has a `@Converter` annotation which you can apply to generated mappers using the `@AnnotateWith` annotation.
+
+.AnnotateWith source example
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@AnnotateWith(
+ value = Converter.class,
+ elements = @AnnotateWith.Element( name = "generateBulkLoader", booleans = true )
+)
+public interface MyConverter {
+ @AnnotateWith( Converter.class )
+ DomainObject map( DtoObject dto );
+}
+----
+====
+
+.AnnotateWith generated mapper
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Converter( generateBulkLoader = true )
+public class MyConverterImpl implements MyConverter {
+ @Converter
+ public DomainObject map( DtoObject dto ) {
+ // default mapping behaviour
+ }
+}
+----
+====
+
+
+[[javadoc]]
+=== Adding Javadoc comments
+
+MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the
+`org.mapstruct.Javadoc` annotation.
+
+This functionality could be relevant especially in situations where certain Javadoc standards need to be met or
+to deal with Javadoc validation constraints.
+
+The `@Javadoc` annotation defines attributes for the different Javadoc elements.
+
+Consider the following example:
+
+.Javadoc annotation example
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ value = "This is the description",
+ authors = { "author1", "author2" },
+ deprecated = "Use {@link OtherMapper} instead",
+ since = "0.1"
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+.Javadoc annotated generated mapper
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+/**
+* This is the description
+*
+* @author author1
+* @author author2
+*
+* @deprecated Use {@link OtherMapper} instead
+* @since 0.1
+*/
+public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+The entire Javadoc comment block can be provided directly as well.
+
+.Javadoc annotation example with the entire Javadoc comment block provided directly
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ "This is the description\n"
+ + "\n"
+ + "@author author1\n"
+ + "@author author2\n"
+ + "\n"
+ + "@deprecated Use {@link OtherMapper} instead\n"
+ + "@since 0.1\n"
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
+
+Or using Text blocks:
+
+.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+@Javadoc(
+ """
+ This is the description
+
+ @author author1
+ @author author2
+
+ @deprecated Use {@link OtherMapper} instead
+ @since 0.1
+ """
+)
+public interface MyAnnotatedWithJavadocMapper {
+ //...
+}
+----
+====
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc
index 724b42fbb7..ec817e0cdf 100644
--- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc
@@ -102,7 +102,7 @@ A mapper which uses other mapper classes (see <>) will o
[[injection-strategy]]
=== Injection strategy
-When using <>, you can choose between field and constructor injection.
+When using <>, you can choose between constructor, field, or setter injection.
This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation.
.Using constructor injection
@@ -117,13 +117,37 @@ public interface CarMapper {
----
====
-The generated mapper will inject all classes defined in the **uses** attribute.
+The generated mapper will inject classes defined in the **uses** attribute if MapStruct has detected that it needs to use an instance of it for a mapping.
When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't.
When `InjectionStrategy#FIELD` is used, the annotation is on the field itself.
+When `InjectionStrategy#SETTER` is used the annotation is on a generated setter method.
For now, the default injection strategy is field injection, but it can be configured with <>.
It is recommended to use constructor injection to simplify testing.
+When you define mappers in Spring with circular dependencies compilation may fail.
+In that case utilize the `InjectionStrategy#SETTER` strategy.
+
[TIP]
====
For abstract classes or decorators setter injection should be used.
-====
\ No newline at end of file
+====
+
+[[class-accessibility]]
+=== Class accessibility
+
+By default, the generated Mapper implementation will have the same accessibility (`public` or none) as the interface or abstract class carrying the `@Mapper` annotation. Especially in conjunction with a DI framework, this might not be desired. If you wish to reduce the visibility of the generated class, you can make use of the `accessibility` attribute like this:
+
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, accessibility = ClassAccessibility.PACKAGE_PRIVATE)
+public interface CarMapper {
+
+ CarDto carToCarDto(Car car);
+}
+
+----
+====
+
+In this case, the generated Mapper will have no modifier. Possible values are `ClassAccessibility.DEFAULT` (the behavior described above), `ClassAccessibility.PUBLIC`, and `ClassAccessibility.PACKAGE_PRIVATE` (no modifier).
diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
index 24f019fa63..10c41506de 100644
--- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc
@@ -43,6 +43,9 @@ public interface CarMapper {
====
* Between `enum` types and `String`.
+* Between `enum` types and `Integer`, according to `enum.ordinal()`.
+** When converting from an `Integer`, the value needs to be less than the number of values of the enum, otherwise an `ArrayOutOfBoundsException` is thrown.
+
* Between big number types (`java.math.BigInteger`, `java.math.BigDecimal`) and Java primitive types (including their wrappers) as well as String. A format string as understood by `java.text.DecimalFormat` can be specified.
.Conversion from BigDecimal to String
@@ -104,6 +107,16 @@ public interface CarMapper {
* Between `java.time.Instant` from Java 8 Date-Time package and `java.util.Date`.
+* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.time.LocalDate` from the same package.
+
+* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.time.LocalDateTime` from the same package.
+
+* Between `java.time.OffsetDateTime` from Java 8 Date-Time package and `java.time.LocalDateTime` from the same package.
+
+* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.time.Instant` from the same package.
+
+* Between `java.time.OffsetDateTime` from Java 8 Date-Time package and `java.time.Instant` from the same package.
+
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`.
* Between `java.sql.Date` and `java.util.Date`
@@ -122,6 +135,23 @@ public interface CarMapper {
* Between `String` and `StringBuilder`
+* Between `java.net.URI` and `String`.
+** When converting from a `String`, the value needs to be a valid https://datatracker.ietf.org/doc/html/rfc2396[RFC 2396 URI] otherwise an `IllegalArgumentException` is thrown.
+
+* Between `java.net.URL` and `String`.
+** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown.
+
+* Between `java.util.Locale` and `String`.
+** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information.
+
+[NOTE]
+====
+All the above conversions also work when the source or target type is wrapped in `java.util.Optional`, `java.util.OptionalDouble`, `java.util.OptionalLong` or `java.util.OptionalInt`.
+For example, `Optional` can be converted to `String`, `int` can be converted to `Optional`,
+or `Optional` can be converted to `Integer`.
+The same conversion rules apply, with MapStruct automatically handling the wrapping and unwrapping of `Optional` values.
+====
+
[[mapping-object-references]]
=== Mapping object references
@@ -269,6 +299,13 @@ This puts the configuration of the nested mapping into one place (method) where
instead of re-configuring the same things on all of those upper methods.
====
+[TIP]
+====
+When ignoring multiple properties instead of defining multiple `@Mapping` annotations, you can use the `@Ignored` annotation to group them together.
+e.g. for the `FishTankMapperWithDocument` example above, you could write:
+`@Ignored(targets = { "plant", "ornament", "material" })`
+====
+
[NOTE]
====
In some cases the `ReportingPolicy` that is going to be used for the generated nested method would be `IGNORE`.
@@ -390,6 +427,39 @@ When having a custom mapper hooked into the generated mapper with `@Mapper#uses(
For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances.
+e.g.
+
+.Example classes for the passing target type example
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+public class Car {
+
+ private Person owner;
+ // ...
+}
+
+public class Person extends BaseEntity {
+
+ // ...
+}
+
+public class Reference {
+
+ private String pk;
+ // ...
+}
+
+public class CarDto {
+
+ private Reference owner;
+ // ...
+}
+----
+====
+
+
.Mapping method expecting mapping target type as parameter
====
[source, java, linenums]
@@ -700,3 +770,73 @@ public interface MovieMapper {
====
Although the used mechanism is the same, the user has to be a bit more careful. Refactoring the name of a defined qualifier in an IDE will neatly refactor all other occurrences as well. This is obviously not the case for changing a name.
====
+
+=== Combining qualifiers with defaults
+Please note that the `Mapping#defaultValue` is in essence a `String`, which needs to be converted to the `Mapping#target`. Providing a `Mapping#qualifiedByName` or `Mapping#qualifiedBy` will force MapStruct to use that method. If you want different behavior for the `Mapping#defaultValue`, then please provide an appropriate mapping method. This mapping method needs to transforms a `String` into the desired type of `Mapping#target` and also be annotated so that it can be found by the `Mapping#qualifiedByName` or `Mapping#qualifiedBy`.
+
+.Mapper using defaultValue
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface MovieMapper {
+
+ @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "DEFAULT" )
+ GermanRelease toGerman( OriginalRelease movies );
+
+ @Named("CategoryToString")
+ default String defaultValueForQualifier(Category cat) {
+ // some mapping logic
+ }
+}
+----
+====
+
+In the above example in case that category is null, the method `CategoryToString( Enum.valueOf( Category.class, "DEFAULT" ) )` will be called and the result will be set to the category field.
+
+.Mapper using defaultValue and default method.
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface MovieMapper {
+
+ @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "Unknown" )
+ GermanRelease toGerman( OriginalRelease movies );
+
+ @Named("CategoryToString")
+ default String defaultValueForQualifier(Category cat) {
+ // some mapping logic
+ }
+
+ @Named("CategoryToString")
+ default String defaultValueForQualifier(String value) {
+ return value;
+ }
+}
+----
+====
+In the above example in case that category is null, the method `defaultValueForQualifier( "Unknown" )` will be called and the result will be set to the category field.
+
+If the above mentioned methods do not work there is the option to use `defaultExpression` to set the default value.
+
+.Mapper using defaultExpression
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface MovieMapper {
+
+ @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultExpression = "java(\"Unknown\")" )
+ GermanRelease toGerman( OriginalRelease movies );
+
+ @Named("CategoryToString")
+ default String defaultValueForQualifier(Category cat) {
+ // some mapping logic
+ }
+}
+----
+====
diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc
index 17025da4de..4510c82cc0 100644
--- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc
@@ -192,7 +192,7 @@ The option `DEFAULT` should not be used explicitly. It is used to distinguish be
[TIP]
====
-When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with intialized collections instead of Mapstruct creating the target entity by its constructor.
+When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with initialized collections instead of Mapstruct creating the target entity by its constructor.
====
[[implementation-types-for-collection-mappings]]
@@ -212,16 +212,20 @@ When an iterable or map mapping method declares an interface type as return type
|`Set`|`LinkedHashSet`
+|`SequencedSet`|`LinkedHashSet`
+
|`SortedSet`|`TreeSet`
|`NavigableSet`|`TreeSet`
|`Map`|`LinkedHashMap`
+|`SequencedMap`|`LinkedHashMap`
+
|`SortedMap`|`TreeMap`
|`NavigableMap`|`TreeMap`
|`ConcurrentMap`|`ConcurrentHashMap`
|`ConcurrentNavigableMap`|`ConcurrentSkipListMap`
-|===
+|===
\ No newline at end of file
diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
index 24c7324f1d..fcb353010d 100644
--- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
@@ -65,7 +65,7 @@ public class OrderMapperImpl implements OrderMapper {
----
====
By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated
-mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs.
+mapping method will throw an `IllegalStateException` if for some reason an unrecognized source value occurs.
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings and only applies to the source. It comes in two flavors: `` and ``. They cannot be used at the same time.
@@ -75,14 +75,18 @@ MapStruct will *not* attempt such name based mapping for `` and di
MapStruct is able to handle `null` sources and `null` targets by means of the `` keyword.
+In addition, the constant value `` can be used for throwing an exception for particular value mappings. This value is only applicable to `ValueMapping#target()` and not `ValueMapping#source()` since MapStruct can't map from exceptions.
+
[TIP]
====
-Constants for ``, `` and `` are available in the `MappingConstants` class.
+Constants for ``, ``, `` and `` are available in the `MappingConstants` class.
====
Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `` and `` will be ignored in that case.
-.Enum mapping method, and
+The following code snippets exemplify the use of the aforementioned constants.
+
+.Enum mapping method, `` and ``
====
[source, java, linenums]
[subs="verbatim,attributes"]
@@ -102,7 +106,7 @@ public interface SpecialOrderMapper {
----
====
-.Enum mapping method result, and
+.Enum mapping method result, `` and ``
====
[source, java, linenums]
[subs="verbatim,attributes"]
@@ -137,9 +141,52 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper {
*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``.
-[WARNING]
+.Enum mapping method with ``
====
-The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead.
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface SpecialOrderMapper {
+
+ SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
+
+ @ValueMappings({
+ @ValueMapping( source = "STANDARD", target = "DEFAULT" ),
+ @ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
+ })
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+}
+----
+====
+
+.Enum mapping method with `` result
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class SpecialOrderMapperImpl implements SpecialOrderMapper {
+
+ @Override
+ public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
+ if ( orderType == null ) {
+ return null;
+ }
+
+ ExternalOrderType externalOrderType;
+
+ switch ( orderType ) {
+ case STANDARD: externalOrderType = ExternalOrderType.DEFAULT;
+ break;
+ case C2C: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
+ default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
+ }
+
+ return externalOrderType;
+ }
+}
+----
====
=== Mapping enum-to-String or String-to-enum
@@ -152,6 +199,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Difference: `` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.
4. Difference: Given 1. and 3. there will never be unmapped values.
+5. Similarity: `` can be used for throwing an exception for particular enum values.
*`String` to enum*
@@ -159,6 +207,7 @@ MapStruct supports enum to a String mapping along the same lines as is described
2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value.
3. Similarity: `` will create a mapping for each target enum constant and proceed to the switch/default clause value.
4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `` or ` will result in a warning.
+5. Similarity: `` can be used for throwing an exception for any arbitrary `String` value.
=== Custom name transformation
@@ -266,3 +315,42 @@ MapStruct provides the following out of the box enum name transformation strateg
It is also possible to register custom strategies.
For more information on how to do that have a look at <>
+
+[[value-mapping-composition]]
+=== ValueMapping Composition
+
+The `@ValueMapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`.
+This allows `@ValueMapping` to be used on other (user defined) annotations for re-use purposes.
+For example:
+
+.Custom value mapping annotations
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Retention( RetentionPolicy.CLASS )
+@ValueMapping(source = "EXTRA", target = "SPECIAL")
+@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
+public @interface CustomValueAnnotation {
+}
+----
+====
+It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example:
+
+.Using custom combination annotations
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface ValueMappingCompositionMapper {
+
+ @CustomValueAnnotation
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+
+ @CustomValueAnnotation
+ @ValueMapping(source = "STANDARD", target = "SPECIAL")
+ ExternalOrderType duplicateAnnotation(OrderType orderType);
+}
+----
+====
diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml
index 4b48c19496..71a61fc2fe 100644
--- a/integrationtest/pom.xml
+++ b/integrationtest/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
@@ -107,19 +107,6 @@
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
-
-
- check-style
- verify
-
- checkstyle
-
-
-
-
@@ -133,10 +120,33 @@
javax.xml.bindjaxb-api
- 2.3.1provided
+ true
+
+ jdk-21-or-newer
+
+ [21
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ check-style
+ verify
+
+ checkstyle
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
index c8bac6208c..7d397c9499 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java
@@ -26,16 +26,42 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces
// SPI not working correctly here.. (not picked up)
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" );
switch ( currentJreVersion ) {
+ case JAVA_8:
+ additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" );
+ if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) {
+ additionalExcludes.add(
+ "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" );
+ }
+ break;
case JAVA_9:
// TODO find out why this fails:
additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
+ break;
+ case JAVA_11:
+ additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
+ break;
+ case JAVA_17:
+ additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
break;
default:
}
- Collection result = new ArrayList( additionalExcludes.size() );
+ Collection result = new ArrayList<>(additionalExcludes.size());
for ( int i = 0; i < additionalExcludes.size(); i++ ) {
result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) );
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java
index 0162582468..a12af7690f 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java
@@ -13,47 +13,49 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.runners.Parameterized.Parameters;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/**
*
This is supposed to be run from the mapstruct root project folder.
* Otherwise, use -Dmapstruct_root=path_to_project.
*/
@DisabledForJreRange(min = JRE.JAVA_11)
-public class GradleIncrementalCompilationTest {
+class GradleIncrementalCompilationTest {
private static Path rootPath;
- private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest";
- private static String compileTaskName = "compileJava";
+ private static final String PROJECT_DIR = "integrationtest/src/test/resources/gradleIncrementalCompilationTest";
+ private static final String COMPILE_TASK_NAME = "compileJava";
@TempDir
- File testBuildDir;
+ private File testBuildDir;
@TempDir
- File testProjectDir;
+ private File testProjectDir;
private GradleRunner runner;
private File sourceDirectory;
private List compileArgs; // Gradle compile task arguments
- @Parameters(name = "Gradle {0}")
- public static List gradleVersions() {
- return Arrays.asList( "5.0", "6.0" );
+ static Stream gradleVersions() {
+ return Stream.of(
+ Arguments.of( Named.of( "Gradle 5.0", "5.0" ) ),
+ Arguments.of( Named.of( "Gradle 6.0", "6.0" ) ) );
}
private void replaceInFile(File file, CharSequence target, CharSequence replacement) throws IOException {
@@ -62,21 +64,19 @@ private void replaceInFile(File file, CharSequence target, CharSequence replacem
}
private GradleRunner getRunner(String... additionalArguments) {
- List fullArguments = new ArrayList( compileArgs );
+ List fullArguments = new ArrayList<>(compileArgs);
fullArguments.addAll( Arrays.asList( additionalArguments ) );
return runner.withArguments( fullArguments );
}
private void assertCompileOutcome(BuildResult result, TaskOutcome outcome) {
- assertEquals( outcome, result.task( ":" + compileTaskName ).getOutcome() );
+ assertEquals( outcome, result.task( ":" + COMPILE_TASK_NAME ).getOutcome() );
}
private void assertRecompiled(BuildResult result, int recompiledCount) {
assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE );
- assertThat(
- result.getOutput(),
- containsString( String.format( "Incremental compilation of %d classes completed", recompiledCount ) )
- );
+ assertThat( result.getOutput() )
+ .contains( String.format( "Incremental compilation of %d classes completed", recompiledCount ) );
}
private List buildCompileArgs() {
@@ -85,11 +85,11 @@ private List buildCompileArgs() {
// Inject the path to the folder containing the mapstruct-processor JAR
String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString();
- return Arrays.asList( compileTaskName, buildDirPropertyArg, jarDirectoryArg );
+ return Arrays.asList( COMPILE_TASK_NAME, buildDirPropertyArg, jarDirectoryArg );
}
@BeforeAll
- public static void setupClass() throws Exception {
+ static void setupClass() {
rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath();
}
@@ -102,7 +102,7 @@ public void setup(String gradleVersion) throws IOException {
testProjectDir.mkdirs();
}
// Copy test project files to the temp dir
- Path gradleProjectPath = rootPath.resolve( projectDir );
+ Path gradleProjectPath = rootPath.resolve( PROJECT_DIR );
FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir );
compileArgs = buildCompileArgs();
sourceDirectory = new File( testProjectDir, "src/main/java" );
@@ -111,7 +111,7 @@ public void setup(String gradleVersion) throws IOException {
@ParameterizedTest
@MethodSource("gradleVersions")
- public void testBuildSucceeds(String gradleVersion) throws IOException {
+ void testBuildSucceeds(String gradleVersion) throws IOException {
setup( gradleVersion );
// Make sure the test build setup actually compiles
BuildResult buildResult = getRunner().build();
@@ -120,7 +120,7 @@ public void testBuildSucceeds(String gradleVersion) throws IOException {
@ParameterizedTest
@MethodSource("gradleVersions")
- public void testUpToDate(String gradleVersion) throws IOException {
+ void testUpToDate(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
BuildResult secondBuildResult = getRunner().build();
@@ -129,7 +129,7 @@ public void testUpToDate(String gradleVersion) throws IOException {
@ParameterizedTest
@MethodSource("gradleVersions")
- public void testChangeConstant(String gradleVersion) throws IOException {
+ void testChangeConstant(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
// Change return value in class Target
@@ -143,7 +143,7 @@ public void testChangeConstant(String gradleVersion) throws IOException {
@ParameterizedTest
@MethodSource("gradleVersions")
- public void testChangeTargetField(String gradleVersion) throws IOException {
+ void testChangeTargetField(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
// Change target field in mapper interface
@@ -157,7 +157,7 @@ public void testChangeTargetField(String gradleVersion) throws IOException {
@ParameterizedTest
@MethodSource("gradleVersions")
- public void testChangeUnrelatedFile(String gradleVersion) throws IOException {
+ void testChangeUnrelatedFile(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" );
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/KotlinFullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/KotlinFullFeatureCompilationExclusionCliEnhancer.java
new file mode 100644
index 0000000000..b4dddfb308
--- /dev/null
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/KotlinFullFeatureCompilationExclusionCliEnhancer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.tests;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.jupiter.api.condition.JRE;
+import org.mapstruct.itest.testutil.extension.ProcessorTest;
+
+/**
+ * Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers.
+ *
+ * @author Filip Hrisafov
+ */
+public final class KotlinFullFeatureCompilationExclusionCliEnhancer implements ProcessorTest.CommandLineEnhancer {
+ @Override
+ public Collection getAdditionalCommandLineArguments(ProcessorTest.ProcessorType processorType,
+ JRE currentJreVersion) {
+ List additionalExcludes = new ArrayList<>();
+
+
+ switch ( currentJreVersion ) {
+ case JAVA_8:
+ addJdkExclude( additionalExcludes, "jdk17" );
+ addJdkExclude( additionalExcludes, "jdk21" );
+ break;
+ case JAVA_11:
+ addJdkExclude( additionalExcludes, "jdk17" );
+ addJdkExclude( additionalExcludes, "jdk21" );
+ break;
+ case JAVA_17:
+ addJdkExclude( additionalExcludes, "jdk21" );
+ break;
+ default:
+ }
+
+ Collection result = new ArrayList<>( additionalExcludes.size() );
+ for ( int i = 0; i < additionalExcludes.size(); i++ ) {
+ result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) );
+ }
+
+ return result;
+ }
+
+ private static void addJdkExclude(Collection additionalExcludes, String jdk) {
+ additionalExcludes.add( "org/mapstruct/ap/test/**/kotlin/**/" + jdk + "/**/*.java" );
+ additionalExcludes.add( "org/mapstruct/ap/test/**/kotlin/**/" + jdk + "/**/*.kt" );
+ }
+}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
index 2ec0ba9aa9..042d6ec1da 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java
@@ -6,6 +6,7 @@
package org.mapstruct.itest.tests;
import org.junit.jupiter.api.condition.DisabledForJreRange;
+import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.parallel.Execution;
@@ -26,7 +27,6 @@ void autoValueBuilderTest() {
}
@ProcessorTest(baseDir = "cdiTest")
- @DisabledForJreRange(min = JRE.JAVA_16)
void cdiTest() {
}
@@ -77,16 +77,31 @@ void java8Test() {
void jaxbTest() {
}
+ @ProcessorTest(baseDir = "jakartaJaxbTest")
+ void jakartaJaxbTest() {
+ }
+
@ProcessorTest(baseDir = "jsr330Test")
+ @EnabledForJreRange(min = JRE.JAVA_17)
void jsr330Test() {
}
@ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
+ @DisabledOnJre(versions = { 27, 28 })
void lombokBuilderTest() {
}
+ @ProcessorTest(baseDir = "lombokModuleTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC,
+ ProcessorTest.ProcessorType.JAVAC_WITH_PATHS
+ })
+ @EnabledForJreRange(min = JRE.JAVA_11)
+ @DisabledOnJre(versions = { 27, 28 })
+ void lombokModuleTest() {
+ }
+
@ProcessorTest(baseDir = "namingStrategyTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
@@ -102,6 +117,11 @@ void namingStrategyTest() {
void protobufBuilderTest() {
}
+ @ProcessorTest(baseDir = "sealedSubclassTest")
+ @EnabledForJreRange(min = JRE.JAVA_17)
+ void sealedSubclassTest() {
+ }
+
@ProcessorTest(baseDir = "recordsTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
@@ -116,19 +136,47 @@ void recordsTest() {
void recordsCrossModuleTest() {
}
+ @ProcessorTest(baseDir = "recordsCrossModuleInterfaceTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC
+ })
+ @EnabledForJreRange(min = JRE.JAVA_17)
+ void recordsCrossModuleInterfaceTest() {
+ }
+
+ @ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC
+ })
+ @EnabledForJreRange(min = JRE.JAVA_17)
+ void expressionTextBlocksTest() {
+ }
+
@ProcessorTest(baseDir = "kotlinDataTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
}, forkJvm = true)
// We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion
// and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907
+ @DisabledOnJre(versions = 27)
void kotlinDataTest() {
}
+ @ProcessorTest(baseDir = "kotlinFullFeatureTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC_WITH_PATHS
+ }, commandLineEnhancer = KotlinFullFeatureCompilationExclusionCliEnhancer.class)
+ @DisabledForJreRange(min = JRE.JAVA_26)
+ void kotlinFullFeatureTest() {
+ }
+
@ProcessorTest(baseDir = "simpleTest")
void simpleTest() {
}
+ // for issue #2593
+ @ProcessorTest(baseDir = "defaultPackage")
+ void defaultPackageTest() {
+ }
+
@ProcessorTest(baseDir = "springTest")
+ @EnabledForJreRange(min = JRE.JAVA_17)
void springTest() {
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
index 89009aaffe..39cd5fdae6 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java
@@ -134,17 +134,18 @@ private void addAdditionalCliArguments(Verifier verifier)
}
private void configureProcessor(Verifier verifier) {
- String compilerId = processorTestContext.getProcessor().getCompilerId();
- if ( compilerId != null ) {
- String profile = processorTestContext.getProcessor().getProfile();
- if ( profile == null ) {
- profile = "generate-via-compiler-plugin";
+ ProcessorTest.ProcessorType processor = processorTestContext.getProcessor();
+ String compilerId = processor.getCompilerId();
+ String profile = processor.getProfile();
+ if ( profile == null ) {
+ profile = "generate-via-compiler-plugin";
+ }
+ verifier.addCliOption( "-P" + profile );
+ verifier.addCliOption( "-Dcompiler-id=" + compilerId );
+ if ( processor == ProcessorTest.ProcessorType.JAVAC ) {
+ if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) {
+ verifier.addCliOption( "-Dmaven.compiler.proc=full" );
}
- verifier.addCliOption( "-P" + profile );
- verifier.addCliOption( "-Dcompiler-id=" + compilerId );
- }
- else {
- verifier.addCliOption( "-Pgenerate-via-processor-plugin" );
}
}
diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
index 95480eee9d..d5b4860d5b 100644
--- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
+++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java
@@ -46,9 +46,7 @@ enum ProcessorType {
JAVAC( "javac" ),
JAVAC_WITH_PATHS( "javac", JRE.OTHER, "generate-via-compiler-plugin-with-annotation-processor-paths" ),
- ECLIPSE_JDT( "jdt", JRE.JAVA_8 ),
-
- PROCESSOR_PLUGIN( null, JRE.JAVA_8 );
+ ECLIPSE_JDT( "jdt", JRE.JAVA_8 );
private final String compilerId;
private final JRE max;
@@ -111,7 +109,6 @@ ProcessorType[] processorTypes() default {
ProcessorType.JAVAC,
ProcessorType.JAVAC_WITH_PATHS,
ProcessorType.ECLIPSE_JDT,
- ProcessorType.PROCESSOR_PLUGIN
};
/**
diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java
index 8f640780f3..4ce5530a5d 100644
--- a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java
+++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.auto.value;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Eric Martineau
*/
-public class AutoValueMapperTest {
+class AutoValueMapperTest {
@Test
- public void testSimpleImmutableBuilderHappyPath() {
+ void testSimpleImmutableBuilderHappyPath() {
PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder()
.age( 33 )
.name( "Bob" )
@@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() {
}
@Test
- public void testLombokToImmutable() {
+ void testLombokToImmutable() {
Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
assertThat( person.getAge() ).isEqualTo( 33 );
assertThat( person.getName() ).isEqualTo( "Bob" );
diff --git a/integrationtest/src/test/resources/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml
index 30d35a421e..cb00fbaa01 100644
--- a/integrationtest/src/test/resources/cdiTest/pom.xml
+++ b/integrationtest/src/test/resources/cdiTest/pom.xml
@@ -30,8 +30,8 @@
javax.inject
- org.jboss.arquillian.junit
- arquillian-junit-container
+ org.jboss.arquillian.junit5
+ arquillian-junit5-containertest
@@ -45,8 +45,8 @@
test
- org.jboss.arquillian.junit
- arquillian-junit-core
+ org.jboss.arquillian.junit5
+ arquillian-junit5-coretest
@@ -56,7 +56,7 @@
org.jboss.weld
- weld-core
+ weld-core-impltest
diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java
index 18a57ce25e..23bb51a6c9 100644
--- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java
+++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.cdi;
+import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
-import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.cdi.other.DateMapper;
@Mapper( componentModel = MappingConstants.ComponentModel.CDI, uses = DateMapper.class )
diff --git a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java
index 702ab6d255..95d2064938 100644
--- a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java
+++ b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java
@@ -5,26 +5,26 @@
*/
package org.mapstruct.itest.cdi;
-import static org.assertj.core.api.Assertions.assertThat;
-
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
-import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.itest.cdi.other.DateMapper;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* Test for generation of CDI-based mapper implementations.
*
* @author Gunnar Morling
*/
-@RunWith( Arquillian.class )
-public class CdiBasedMapperTest {
+@ExtendWith( ArquillianExtension.class )
+class CdiBasedMapperTest {
@Inject
private SourceTargetMapper mapper;
@@ -38,12 +38,14 @@ public static JavaArchive createDeployment() {
.addPackage( SourceTargetMapper.class.getPackage() )
.addPackage( DateMapper.class.getPackage() )
.addAsManifestResource(
- new StringAsset("org.mapstruct.itest.cdi.SourceTargetMapperDecorator"),
+ new StringAsset("" +
+ "org.mapstruct.itest.cdi.SourceTargetMapperDecorator" +
+ ""),
"beans.xml" );
}
@Test
- public void shouldCreateCdiBasedMapper() {
+ void shouldCreateCdiBasedMapper() {
Source source = new Source();
Target target = mapper.sourceToTarget( source );
@@ -54,7 +56,7 @@ public void shouldCreateCdiBasedMapper() {
}
@Test
- public void shouldInjectDecorator() {
+ void shouldInjectDecorator() {
Source source = new Source();
Target target = decoratedMapper.sourceToTarget( source );
diff --git a/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java b/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java
new file mode 100644
index 0000000000..ba684850b4
--- /dev/null
+++ b/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class DefaultPackageObject {
+ public enum CarType {
+ SEDAN, CAMPER, X4, TRUCK;
+ }
+
+ static public class Car {
+
+ private String make;
+ private int numberOfSeats;
+ private CarType type;
+
+ public Car(String string, int numberOfSeats, CarType sedan) {
+ this.make = string;
+ this.numberOfSeats = numberOfSeats;
+ this.type = sedan;
+ }
+
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public int getNumberOfSeats() {
+ return numberOfSeats;
+ }
+
+ public void setNumberOfSeats(int numberOfSeats) {
+ this.numberOfSeats = numberOfSeats;
+ }
+
+ public CarType getType() {
+ return type;
+ }
+
+ public void setType(CarType type) {
+ this.type = type;
+ }
+ }
+
+ static public class CarDto {
+
+ private String make;
+ private int seatCount;
+ private String type;
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public int getSeatCount() {
+ return seatCount;
+ }
+
+ public void setSeatCount(int seatCount) {
+ this.seatCount = seatCount;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+ }
+
+
+ @Mapper
+ public interface CarMapper {
+
+ CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
+
+ @Mapping(source = "numberOfSeats", target = "seatCount")
+ CarDto carToCarDto(Car car);
+ }
+}
diff --git a/integrationtest/src/test/resources/defaultPackage/pom.xml b/integrationtest/src/test/resources/defaultPackage/pom.xml
new file mode 100644
index 0000000000..d72300f618
--- /dev/null
+++ b/integrationtest/src/test/resources/defaultPackage/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ defaultPackage
+ jar
+
diff --git a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java
new file mode 100644
index 0000000000..c3f84b6ab6
--- /dev/null
+++ b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ */
+
+class DefaultPackageTest {
+
+ @Test
+ void shouldWorkCorrectlyInDefaultPackage() {
+ DefaultPackageObject.CarDto carDto = DefaultPackageObject.CarMapper.INSTANCE.carToCarDto(
+ new DefaultPackageObject.Car(
+ "Morris",
+ 5,
+ DefaultPackageObject.CarType.SEDAN
+ ) );
+
+ assertThat( carDto ).isNotNull();
+ assertThat( carDto.getMake() ).isEqualTo( "Morris" );
+ assertThat( carDto.getSeatCount() ).isEqualTo( 5 );
+ assertThat( carDto.getType() ).isEqualTo( "SEDAN" );
+ }
+}
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml b/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml
new file mode 100644
index 0000000000..b70c48f9d0
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ expressionTextBlocksTest
+ jar
+
+
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java
new file mode 100644
index 0000000000..28d4fd3cd5
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.textBlocks;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Car {
+
+ private WheelPosition wheelPosition;
+
+ public WheelPosition getWheelPosition() {
+ return wheelPosition;
+ }
+
+ public void setWheelPosition(WheelPosition wheelPosition) {
+ this.wheelPosition = wheelPosition;
+ }
+}
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java
new file mode 100644
index 0000000000..23d665c6ff
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.textBlocks;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface CarAndWheelMapper {
+
+ CarAndWheelMapper INSTANCE = Mappers.getMapper( CarAndWheelMapper.class );
+
+ @Mapping(target = "wheelPosition",
+ expression =
+ """
+ java(
+ source.getWheelPosition() == null ?
+ null :
+ source.getWheelPosition().getPosition()
+ )
+ """)
+ CarDto carDtoFromCar(Car source);
+
+ @Mapping(target = "wheelPosition",
+ expression = """
+ java(
+ source.wheelPosition() == null ?
+ null :
+ new WheelPosition(source.wheelPosition())
+ )
+ """)
+ Car carFromCarDto(CarDto source);
+}
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java
new file mode 100644
index 0000000000..f4baa4d044
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.textBlocks;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record CarDto(String wheelPosition) {
+
+}
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java
new file mode 100644
index 0000000000..6effd485a3
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.textBlocks;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class WheelPosition {
+
+ private final String position;
+
+ public WheelPosition(String position) {
+ this.position = position;
+ }
+
+ public String getPosition() {
+ return position;
+ }
+}
diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java
new file mode 100644
index 0000000000..245cf8f304
--- /dev/null
+++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.textBlocks;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TextBlocksTest {
+
+ @Test
+ void textBlockExpressionShouldWork() {
+ Car car = new Car();
+ car.setWheelPosition( new WheelPosition( "left" ) );
+
+ CarDto carDto = CarAndWheelMapper.INSTANCE.carDtoFromCar(car);
+
+ assertThat( carDto ).isNotNull();
+ assertThat( carDto.wheelPosition() )
+ .isEqualTo( "left" );
+ }
+}
diff --git a/integrationtest/src/test/resources/externalbeanjar/mapper/src/main/java/org/mapstruct/itest/externalbeanjar/Issue1121Mapper.java b/integrationtest/src/test/resources/externalbeanjar/mapper/src/main/java/org/mapstruct/itest/externalbeanjar/Issue1121Mapper.java
index 028941e24d..35cfacdb89 100644
--- a/integrationtest/src/test/resources/externalbeanjar/mapper/src/main/java/org/mapstruct/itest/externalbeanjar/Issue1121Mapper.java
+++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/main/java/org/mapstruct/itest/externalbeanjar/Issue1121Mapper.java
@@ -10,11 +10,11 @@
*/
package org.mapstruct.itest.externalbeanjar;
-import org.mapstruct.itest.externalbeanjar.Source;
-import org.mapstruct.itest.externalbeanjar.Target;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
+import org.mapstruct.itest.externalbeanjar.Source;
+import org.mapstruct.itest.externalbeanjar.Target;
/**
*
diff --git a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java
index 8a18e9e4fc..5d12c1b0f2 100644
--- a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java
+++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java
@@ -6,17 +6,18 @@
package org.mapstruct.itest.simple;
import java.math.BigDecimal;
-import static org.assertj.core.api.Assertions.assertThat;
-import org.junit.Test;
-import org.mapstruct.itest.externalbeanjar.Source;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.externalbeanjar.Issue1121Mapper;
+import org.mapstruct.itest.externalbeanjar.Source;
import org.mapstruct.itest.externalbeanjar.Target;
-public class ConversionTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ConversionTest {
@Test
- public void shouldApplyConversions() {
+ void shouldApplyConversions() {
Source source = new Source();
source.setBigDecimal( new BigDecimal( "42" ) );
diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml
index 08390085dd..60b73b856c 100644
--- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml
+++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml
@@ -27,8 +27,8 @@
provided
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml
index 90c95aa83b..976664f88d 100644
--- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml
+++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java
index 06bfa3c533..ed6a5c0d89 100644
--- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java
+++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.faultyAstModifyingProcessor.usage;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Filip Hrisafov
*/
-public class FaultyAstModifyingTestTest {
+class FaultyAstModifyingTestTest {
@Test
- public void testMapping() {
+ void testMapping() {
Order order = new Order();
order.setItem( "my item" );
diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java
index 5047451798..3b1caebf9c 100644
--- a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java
+++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.freebuilder;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Eric Martineau
*/
-public class FreeBuilderMapperTest {
+class FreeBuilderMapperTest {
@Test
- public void testSimpleImmutableBuilderHappyPath() {
+ void testSimpleImmutableBuilderHappyPath() {
PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder()
.setAge( 33 )
.setName( "Bob" )
@@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() {
}
@Test
- public void testLombokToImmutable() {
+ void testLombokToImmutable() {
Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
assertThat( person.getAge() ).isEqualTo( 33 );
assertThat( person.getName() ).isEqualTo( "Bob" );
diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
index ebf4590d30..670d5cff92 100644
--- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml
+++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml
@@ -25,6 +25,13 @@
xxx
+ x
+ x
+ x
+ x
+ x
+ x
+ x
@@ -40,11 +47,21 @@
**/*Test.java**/testutil/**/*.java**/spi/**/*.java
+ **/kotlin/**/*.java${additionalExclude0}${additionalExclude1}${additionalExclude2}${additionalExclude3}${additionalExclude4}
+ ${additionalExclude5}
+ ${additionalExclude6}
+ ${additionalExclude7}
+ ${additionalExclude8}
+ ${additionalExclude9}
+ ${additionalExclude10}
+ ${additionalExclude11}
+ ${additionalExclude12}
+ ${additionalExclude13}
@@ -60,6 +77,14 @@
javax.injectjavax.inject
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+
@@ -75,6 +100,13 @@
joda-timejoda-time
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ provided
+ true
+
@@ -85,14 +117,16 @@
- jakarta.xml.bind
- jakarta.xml.bind-api
- 2.3.2
+ javax.xml.bind
+ jaxb-api
+ provided
+ trueorg.glassfish.jaxbjaxb-runtime
- 2.3.2
+ provided
+ true
diff --git a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java
index a180b4df52..4f2d7bd9ed 100644
--- a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java
+++ b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java
@@ -5,23 +5,22 @@
*/
package org.mapstruct.ap.test.ignore;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
-
-import org.mapstruct.ap.test.ignore.AnimalMapper;
+import org.junit.jupiter.api.Test;
import org.mapstruct.ap.test.ignore.Animal;
import org.mapstruct.ap.test.ignore.AnimalDto;
+import org.mapstruct.ap.test.ignore.AnimalMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for ignoring properties during the mapping.
*
* @author Gunnar Morling
*/
-public class AnimalTest {
+class AnimalTest {
@Test
- public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() {
+ void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() {
Animal animal = new Animal( "Bruno", 100, 23, "black" );
AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal );
@@ -34,7 +33,7 @@ public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() {
}
@Test
- public void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() {
+ void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() {
AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23, "black" );
Animal animal = AnimalMapper.INSTANCE.animalDtoToAnimal( animalDto );
diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle
index e62a8d087f..0df032f0f2 100644
--- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle
+++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle
@@ -1,3 +1,8 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
plugins {
id 'java'
}
diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle
index f62a77ab7b..b6ca918e91 100644
--- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle
+++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle
@@ -1 +1,6 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
rootProject.name = 'gradle-incremental-compilation-test'
\ No newline at end of file
diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java
index 6137ff0eca..52c64051f1 100644
--- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java
+++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.java
@@ -8,9 +8,8 @@
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
-
-import org.mapstruct.itest.gradle.model.Target;
import org.mapstruct.itest.gradle.model.Source;
+import org.mapstruct.itest.gradle.model.Target;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface TestMapper {
diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java
index 421954fc90..9990f4278c 100644
--- a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java
+++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.immutables;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Eric Martineau
*/
-public class ImmutablesMapperTest {
+class ImmutablesMapperTest {
@Test
- public void testSimpleImmutableBuilderHappyPath() {
+ void testSimpleImmutableBuilderHappyPath() {
PersonDto personDto = PersonMapper.INSTANCE.toDto( ImmutablePerson.builder()
.age( 33 )
.name( "Bob" )
@@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() {
}
@Test
- public void testLombokToImmutable() {
+ void testLombokToImmutable() {
Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
assertThat( person.getAge() ).isEqualTo( 33 );
assertThat( person.getName() ).isEqualTo( "Bob" );
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml b/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml
new file mode 100644
index 0000000000..3a2d0351ce
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ jakartaJaxbTest
+ jar
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ provided
+ true
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ provided
+ true
+
+
+
+
+
+
+ org.codehaus.mojo
+ jaxb2-maven-plugin
+ 3.1.0
+
+
+ xjc
+ initialize
+
+ xjc
+
+
+
+
+
+ \${project.build.resources[0].directory}/binding
+
+
+ \${project.build.resources[0].directory}/schema
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java
new file mode 100644
index 0000000000..e8500633e4
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+import java.util.List;
+
+/**
+ * @author Sjaak Derksen
+ */
+public class OrderDetailsDto {
+
+ private String name;
+ private List description;
+ private OrderStatusDto status;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getDescription() {
+ return description;
+ }
+
+ public void setDescription(List description) {
+ this.description = description;
+ }
+
+ public OrderStatusDto getStatus() {
+ return status;
+ }
+
+ public void setStatus(OrderStatusDto status) {
+ this.status = status;
+ }
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java
new file mode 100644
index 0000000000..f94d5362ef
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+import java.util.Date;
+
+/**
+ * @author Sjaak Derksen
+ */
+public class OrderDto {
+
+ private Long orderNumber;
+ private Date orderDate;
+ private OrderDetailsDto orderDetails;
+ private ShippingAddressDto shippingAddress;
+
+ public Long getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(Long orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public Date getOrderDate() {
+ return orderDate;
+ }
+
+ public void setOrderDate(Date orderDate) {
+ this.orderDate = orderDate;
+ }
+
+ public OrderDetailsDto getOrderDetails() {
+ return orderDetails;
+ }
+
+ public void setOrderDetails(OrderDetailsDto orderDetails) {
+ this.orderDetails = orderDetails;
+ }
+
+ public ShippingAddressDto getShippingAddress() {
+ return shippingAddress;
+ }
+
+ public void setShippingAddress(ShippingAddressDto shippingAddress) {
+ this.shippingAddress = shippingAddress;
+ }
+
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java
new file mode 100644
index 0000000000..5da5d45c99
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+/**
+ * @author Sjaak Derksen
+ */
+public enum OrderStatusDto {
+
+ ORDERED( "small" ),
+ PROCESSED( "medium" ),
+ DELIVERED( "large" );
+ private final String value;
+
+ OrderStatusDto(String v) {
+ value = v;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public static OrderStatusDto fromValue(String v) {
+ for ( OrderStatusDto c : OrderStatusDto.values() ) {
+ if ( c.value.equals( v ) ) {
+ return c;
+ }
+ }
+ throw new IllegalArgumentException( v );
+ }
+
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java
new file mode 100644
index 0000000000..6bc40a19b2
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+/**
+ * @author Sjaak Derksen
+ */
+public class ShippingAddressDto {
+
+ private String street;
+ private String houseNumber;
+ private String city;
+ private String country;
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getHouseNumber() {
+ return houseNumber;
+ }
+
+ public void setHouseNumber(String houseNumber) {
+ this.houseNumber = houseNumber;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java
new file mode 100644
index 0000000000..8356f76192
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderDetailsType;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test2.OrderStatusType;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test2.ShippingAddressType;
+import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper(uses = {
+ org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory.class,
+ org.mapstruct.itest.jakarta.jaxb.xsd.test2.ObjectFactory.class,
+ org.mapstruct.itest.jakarta.jaxb.xsd.underscores.ObjectFactory.class
+})
+public interface SourceTargetMapper {
+
+ SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
+
+ // source 2 target methods
+ OrderDto sourceToTarget(OrderType source);
+
+ OrderDetailsDto detailsToDto(OrderDetailsType source);
+
+ OrderStatusDto statusToDto(OrderStatusType source);
+
+ ShippingAddressDto shippingAddressToDto(ShippingAddressType source);
+
+ SubTypeDto subTypeToDto(SubType source);
+
+ // target 2 source methods
+ OrderType targetToSource(OrderDto target);
+
+ OrderDetailsType dtoToDetails(OrderDetailsDto target);
+
+ OrderStatusType dtoToStatus(OrderStatusDto target);
+
+ ShippingAddressType dtoToShippingAddress(ShippingAddressDto source);
+
+ SubType dtoToSubType(SubTypeDto source);
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java
new file mode 100644
index 0000000000..88218c2771
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+public class SubTypeDto extends SuperTypeDto {
+ private String declaredCamelCase;
+ private String declaredUnderscore;
+
+ public String getDeclaredCamelCase() {
+ return declaredCamelCase;
+ }
+
+ public void setDeclaredCamelCase(String declaredCamelCase) {
+ this.declaredCamelCase = declaredCamelCase;
+ }
+
+ public String getDeclaredUnderscore() {
+ return declaredUnderscore;
+ }
+
+ public void setDeclaredUnderscore(String declaredUnderscore) {
+ this.declaredUnderscore = declaredUnderscore;
+ }
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java
new file mode 100644
index 0000000000..cd0c6e22e7
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+public class SuperTypeDto {
+ private String inheritedCamelCase;
+ private String inheritedUnderscore;
+
+ public String getInheritedCamelCase() {
+ return inheritedCamelCase;
+ }
+
+ public void setInheritedCamelCase(String inheritedCamelCase) {
+ this.inheritedCamelCase = inheritedCamelCase;
+ }
+
+ public String getInheritedUnderscore() {
+ return inheritedUnderscore;
+ }
+
+ public void setInheritedUnderscore(String inheritedUnderscore) {
+ this.inheritedUnderscore = inheritedUnderscore;
+ }
+}
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb
new file mode 100644
index 0000000000..8f26b1a1ea
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd
new file mode 100644
index 0000000000..3433b01465
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd
new file mode 100644
index 0000000000..f3b564a48e
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd
new file mode 100644
index 0000000000..b7f5904656
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java
new file mode 100644
index 0000000000..78e051a4e9
--- /dev/null
+++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.jakarta.jaxb;
+
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBElement;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory;
+import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType;
+import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for generation of Jakarta JAXB based mapper implementations.
+ *
+ * @author Iaroslav Bogdanchikov
+ */
+class JakartaJaxbBasedMapperTest {
+
+ @Test
+ void shouldMapJakartaJaxb() throws ParseException, JAXBException {
+
+ SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
+
+ OrderDto source1 = new OrderDto();
+ source1.setOrderDetails( new OrderDetailsDto() );
+ source1.setOrderNumber( 11L );
+ source1.setOrderDate( createDate( "31-08-1982 10:20:56" ) );
+ source1.setShippingAddress( new ShippingAddressDto() );
+ source1.getShippingAddress().setCity( "SmallTown" );
+ source1.getShippingAddress().setHouseNumber( "11a" );
+ source1.getShippingAddress().setStreet( "Awesome rd" );
+ source1.getShippingAddress().setCountry( "USA" );
+ source1.getOrderDetails().setDescription( new ArrayList() );
+ source1.getOrderDetails().setName( "Shopping list for a Mapper" );
+ source1.getOrderDetails().getDescription().add( "1 MapStruct" );
+ source1.getOrderDetails().getDescription().add( "3 Lines of Code" );
+ source1.getOrderDetails().getDescription().add( "1 Dose of Luck" );
+ source1.getOrderDetails().setStatus( OrderStatusDto.ORDERED );
+
+ // map to JAXB
+ OrderType target = mapper.targetToSource( source1 );
+
+ // do a pretty print
+ ObjectFactory of = new ObjectFactory();
+ System.out.println( toXml( of.createOrder( target ) ) );
+
+ // map back from JAXB
+ OrderDto source2 = mapper.sourceToTarget( target );
+
+ // verify that source1 and source 2 are equal
+ assertThat( source2.getOrderNumber() ).isEqualTo( source1.getOrderNumber() );
+ assertThat( source2.getOrderDate() ).isEqualTo( source1.getOrderDate() );
+ assertThat( source2.getOrderDetails().getDescription().size() ).isEqualTo(
+ source1.getOrderDetails().getDescription().size()
+ );
+ assertThat( source2.getOrderDetails().getDescription().get( 0 ) ).isEqualTo(
+ source1.getOrderDetails().getDescription().get( 0 )
+ );
+ assertThat( source2.getOrderDetails().getDescription().get( 1 ) ).isEqualTo(
+ source1.getOrderDetails().getDescription().get( 1 )
+ );
+ assertThat( source2.getOrderDetails().getDescription().get( 2 ) ).isEqualTo(
+ source1.getOrderDetails().getDescription().get( 2 )
+ );
+ assertThat( source2.getOrderDetails().getName() ).isEqualTo( source1.getOrderDetails().getName() );
+ assertThat( source2.getOrderDetails().getStatus() ).isEqualTo( source1.getOrderDetails().getStatus() );
+ }
+
+ @Test
+ void underscores() throws ParseException, JAXBException {
+
+ SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
+
+ SubTypeDto source1 = new SubTypeDto();
+ source1.setInheritedCamelCase("InheritedCamelCase");
+ source1.setInheritedUnderscore("InheritedUnderscore");
+ source1.setDeclaredCamelCase("DeclaredCamelCase");
+ source1.setDeclaredUnderscore("DeclaredUnderscore");
+
+ SubType target = mapper.dtoToSubType( source1 );
+
+ SubTypeDto source2 = mapper.subTypeToDto( target );
+
+ assertThat( source2.getInheritedCamelCase() ).isEqualTo( source1.getInheritedCamelCase() );
+ assertThat( source2.getInheritedUnderscore() ).isEqualTo( source1.getInheritedUnderscore() );
+ assertThat( source2.getDeclaredCamelCase() ).isEqualTo( source1.getDeclaredCamelCase() );
+ assertThat( source2.getDeclaredUnderscore() ).isEqualTo( source1.getDeclaredUnderscore() );
+ }
+
+ private Date createDate(String date) throws ParseException {
+ SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" );
+ return sdf.parse( date );
+ }
+
+ private String toXml(JAXBElement> element) throws JAXBException {
+ JAXBContext jc = JAXBContext.newInstance( element.getValue().getClass() );
+ Marshaller marshaller = jc.createMarshaller();
+ marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ marshaller.marshal( element, baos );
+ return baos.toString();
+ }
+}
diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java
index c732b96f22..efe68a48ee 100644
--- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java
+++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java
@@ -5,15 +5,14 @@
*/
package org.mapstruct.ap.test.bugs._603;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
-public class Issue603Test {
+class Issue603Test {
@Test
- public void shouldMapDataFromJava8Interface() {
+ void shouldMapDataFromJava8Interface() {
final Source source = new Source();
diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java
index 99b7a515eb..837eb0b53a 100644
--- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java
+++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java
@@ -5,14 +5,14 @@
*/
package org.mapstruct.ap.test.bugs._636;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
-public class Issue636Test {
+class Issue636Test {
@Test
- public void shouldMapDataFromJava8Interface() {
+ void shouldMapDataFromJava8Interface() {
final long idFoo = 123;
final String idBar = "Bar456";
diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java
index 556dd2f1e4..4a390628af 100644
--- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java
+++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java
@@ -5,19 +5,19 @@
*/
package org.mapstruct.itest.java8;
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.Test;
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for generation of Java8 based mapper implementations.
*
* @author Christian Schuster
*/
-public class Java8MapperTest {
+class Java8MapperTest {
@Test
- public void shouldMapWithRepeatedMappingAnnotation() {
+ void shouldMapWithRepeatedMappingAnnotation() {
Java8Mapper mapper = Java8Mapper.INSTANCE;
Source source = new Source();
diff --git a/integrationtest/src/test/resources/jaxbTest/pom.xml b/integrationtest/src/test/resources/jaxbTest/pom.xml
index 000e7cf598..0e69e23e01 100644
--- a/integrationtest/src/test/resources/jaxbTest/pom.xml
+++ b/integrationtest/src/test/resources/jaxbTest/pom.xml
@@ -51,7 +51,7 @@
org.glassfish.jaxbjaxb-runtime
- 2.3.2
+ ${jaxb-runtime.version}
@@ -66,14 +66,16 @@
- jakarta.xml.bind
- jakarta.xml.bind-api
- 2.3.2
+ javax.xml.bind
+ jaxb-api
+ provided
+ trueorg.glassfish.jaxbjaxb-runtime
- 2.3.2
+ provided
+ true
diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SourceTargetMapper.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SourceTargetMapper.java
index c0a56f8169..49dab0092f 100644
--- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SourceTargetMapper.java
+++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SourceTargetMapper.java
@@ -13,7 +13,6 @@
import org.mapstruct.itest.jaxb.xsd.test2.ShippingAddressType;
import org.mapstruct.itest.jaxb.xsd.underscores.SubType;
-
/**
* @author Sjaak Derksen
*/
diff --git a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java
index f54658ba06..d54484334b 100644
--- a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java
+++ b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java
@@ -5,32 +5,31 @@
*/
package org.mapstruct.itest.jaxb;
-import static org.assertj.core.api.Assertions.assertThat;
-
import java.io.ByteArrayOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
-
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory;
import org.mapstruct.itest.jaxb.xsd.test1.OrderType;
import org.mapstruct.itest.jaxb.xsd.underscores.SubType;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* Test for generation of JAXB based mapper implementations.
*
* @author Sjaak Derksen
*/
-public class JaxbBasedMapperTest {
+class JaxbBasedMapperTest {
@Test
- public void shouldMapJaxb() throws ParseException, JAXBException {
+ void shouldMapJaxb() throws ParseException, JAXBException {
SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
@@ -80,7 +79,7 @@ public void shouldMapJaxb() throws ParseException, JAXBException {
}
@Test
- public void underscores() throws ParseException, JAXBException {
+ void underscores() throws ParseException, JAXBException {
SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
diff --git a/integrationtest/src/test/resources/jsr330Test/pom.xml b/integrationtest/src/test/resources/jsr330Test/pom.xml
index c0372831f1..e1cf959a90 100644
--- a/integrationtest/src/test/resources/jsr330Test/pom.xml
+++ b/integrationtest/src/test/resources/jsr330Test/pom.xml
@@ -18,6 +18,10 @@
jsr330Testjar
+
+
+ ${org.junit6.jupiter.version}
+
@@ -34,8 +38,8 @@
spring-context
- javax.inject
- javax.inject
+ jakarta.inject
+ jakarta.inject-api
diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java
index 77f5ec558a..3f04abd7df 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.jsr330;
+import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
-import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.jsr330.other.DateMapper;
@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = DateMapper.class)
diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java
index f088e21de4..edfaeb3171 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.jsr330;
+import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
-import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.jsr330.other.DateMapper;
@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = DateMapper.class)
diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java
index d41db89dd5..0fb513c99b 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java
@@ -5,8 +5,8 @@
*/
package org.mapstruct.itest.jsr330;
-import javax.inject.Inject;
-import javax.inject.Named;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
public abstract class SecondSourceTargetMapperDecorator implements SecondDecoratedSourceTargetMapper {
diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java
index e7f3423e4b..7f4b968227 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java
@@ -5,8 +5,8 @@
*/
package org.mapstruct.itest.jsr330;
-import javax.inject.Inject;
-import javax.inject.Named;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
public abstract class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java
index 817ab2aa53..d97b168776 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java
@@ -9,8 +9,8 @@
import java.text.SimpleDateFormat;
import java.util.Date;
-import javax.inject.Named;
-import javax.inject.Singleton;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
@Singleton
@Named
diff --git a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java
index 26a9863576..7196051ff8 100644
--- a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java
+++ b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java
@@ -5,16 +5,15 @@
*/
package org.mapstruct.itest.jsr330;
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.itest.jsr330.Jsr330BasedMapperTest.SpringTestConfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@@ -24,8 +23,8 @@
* @author Andreas Gudian
*/
@ContextConfiguration(classes = SpringTestConfig.class)
-@RunWith(SpringJUnit4ClassRunner.class)
-public class Jsr330BasedMapperTest {
+@ExtendWith(SpringExtension.class)
+class Jsr330BasedMapperTest {
@Configuration
@ComponentScan(basePackageClasses = Jsr330BasedMapperTest.class)
public static class SpringTestConfig {
@@ -43,7 +42,7 @@ public static class SpringTestConfig {
private SecondDecoratedSourceTargetMapper secondDecoratedMapper;
@Test
- public void shouldInjectJsr330BasedMapper() {
+ void shouldInjectJsr330BasedMapper() {
Source source = new Source();
Target target = mapper.sourceToTarget( source );
@@ -54,7 +53,7 @@ public void shouldInjectJsr330BasedMapper() {
}
@Test
- public void shouldInjectDecorator() {
+ void shouldInjectDecorator() {
Source source = new Source();
Target target = decoratedMapper.sourceToTarget( source );
@@ -71,7 +70,7 @@ public void shouldInjectDecorator() {
}
@Test
- public void shouldInjectSecondDecorator() {
+ void shouldInjectSecondDecorator() {
Source source = new Source();
Target target = secondDecoratedMapper.sourceToTarget( source );
diff --git a/integrationtest/src/test/resources/kotlinDataTest/pom.xml b/integrationtest/src/test/resources/kotlinDataTest/pom.xml
index 8a43a04194..f86f92b9c4 100644
--- a/integrationtest/src/test/resources/kotlinDataTest/pom.xml
+++ b/integrationtest/src/test/resources/kotlinDataTest/pom.xml
@@ -22,7 +22,7 @@
jar
- 1.3.70
+ 1.6.0
diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java
index 513f080a8b..39bb909746 100644
--- a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java
+++ b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java
@@ -5,17 +5,17 @@
*/
package org.mapstruct.itest.kotlin.data;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.kotlin.data.CustomerDto;
import org.mapstruct.itest.kotlin.data.CustomerEntity;
import org.mapstruct.itest.kotlin.data.CustomerMapper;
-public class KotlinDataTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class KotlinDataTest {
@Test
- public void shouldMapData() {
+ void shouldMapData() {
CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) );
assertThat( customer ).isNotNull();
@@ -24,7 +24,7 @@ public void shouldMapData() {
}
@Test
- public void shouldMapIntoData() {
+ void shouldMapIntoData() {
CustomerEntity entity = new CustomerEntity();
entity.setName( "Kermit" );
entity.setMail( "kermit@test.com" );
diff --git a/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml
new file mode 100644
index 0000000000..f481fd6963
--- /dev/null
+++ b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml
@@ -0,0 +1,186 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ kotlinFullFeatureTest
+ jar
+
+
+ 2.3.0
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+
+
+
+
+ generate-via-compiler-plugin-with-annotation-processor-paths
+
+ false
+
+
+
+
+ maven-resources-plugin
+
+
+ filter-kotlin-patterns
+ generate-sources
+
+ copy-resources
+
+
+ \${project.build.directory}/kotlin-sources
+
+
+ ../../../../../processor/src/test/java
+
+ **/kotlin/**/*.kt
+
+
+ ${additionalExclude0}
+ ${additionalExclude1}
+ ${additionalExclude2}
+ ${additionalExclude3}
+ ${additionalExclude4}
+ ${additionalExclude5}
+ ${additionalExclude6}
+ ${additionalExclude7}
+ ${additionalExclude8}
+ ${additionalExclude9}
+ ${additionalExclude10}
+ ${additionalExclude11}
+ ${additionalExclude12}
+ ${additionalExclude13}
+
+
+
+ true
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ kotlin-compile
+ compile
+
+ compile
+
+
+ \${compiler-source-target-version}
+
+ \${project.build.directory}/kotlin-sources
+
+
+
+ ${project.groupId}
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ ../../../../../processor/src/test/java
+
+
+ **/kotlin/**/*.java
+
+
+ **/erroneous/**/*.java
+ **/*Erroneous*.java
+ **/*Test.java
+ **/testutil/**/*.java
+ ${additionalExclude0}
+ ${additionalExclude1}
+ ${additionalExclude2}
+ ${additionalExclude3}
+ ${additionalExclude4}
+ ${additionalExclude5}
+ ${additionalExclude6}
+ ${additionalExclude7}
+ ${additionalExclude8}
+ ${additionalExclude9}
+ ${additionalExclude10}
+ ${additionalExclude11}
+ ${additionalExclude12}
+ ${additionalExclude13}
+
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+
+
+
+
diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java
index 6053f294c3..060fb5a990 100644
--- a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java
+++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.lombok;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import static org.assertj.core.api.Assertions.assertThat;
@@ -15,10 +15,10 @@
*
* @author Eric Martineau
*/
-public class LombokMapperTest {
+class LombokMapperTest {
@Test
- public void testSimpleImmutableBuilderHappyPath() {
+ void testSimpleImmutableBuilderHappyPath() {
PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.foo()
.age( 33 )
.name( "Bob" )
@@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() {
}
@Test
- public void testLombokToImmutable() {
+ void testLombokToImmutable() {
Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
assertThat( person.getAge() ).isEqualTo( 33 );
assertThat( person.getName() ).isEqualTo( "Bob" );
diff --git a/integrationtest/src/test/resources/lombokModuleTest/pom.xml b/integrationtest/src/test/resources/lombokModuleTest/pom.xml
new file mode 100644
index 0000000000..4280b38417
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/pom.xml
@@ -0,0 +1,85 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ lombokModuleIntegrationTest
+ jar
+
+
+ 11
+ 11
+
+
+
+
+ org.projectlombok
+ lombok
+ compile
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+ compile
+
+
+
+
+
+ generate-via-compiler-plugin-with-annotation-processor-paths
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ \${compiler-id}
+
+
+ ${project.groupId}
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-jdt
+ ${org.eclipse.tycho.compiler-jdt.version}
+
+
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java
new file mode 100644
index 0000000000..9bc85bea86
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+module org.example {
+ requires org.mapstruct;
+ requires lombok;
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java
new file mode 100644
index 0000000000..f83c6881ce
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+public class Address {
+ private final String addressLine;
+
+ public Address(String addressLine) {
+ this.addressLine = addressLine;
+ }
+
+ public String getAddressLine() {
+ return addressLine;
+ }
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java
new file mode 100644
index 0000000000..d6b4f30f88
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+public class AddressDto {
+
+ private final String addressLine;
+
+ public AddressDto(String addressLine) {
+ this.addressLine = addressLine;
+ }
+
+ public String getAddressLine() {
+ return addressLine;
+ }
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java
new file mode 100644
index 0000000000..b6ca47fca1
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+public class Person {
+ private final String name;
+ private final int age;
+ private final Address address;
+
+ public Person(String name, int age, Address address) {
+ this.name = name;
+ this.age = age;
+ this.address = address;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java
new file mode 100644
index 0000000000..3223778663
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+public class PersonDto {
+ private final String name;
+ private final int age;
+ private final AddressDto address;
+
+ public PersonDto(String name, int age, AddressDto address) {
+ this.name = name;
+ this.age = age;
+ this.address = address;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public AddressDto getAddress() {
+ return address;
+ }
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java
new file mode 100644
index 0000000000..27184bad27
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface PersonMapper {
+
+ PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
+
+ Person fromDto(PersonDto personDto);
+ PersonDto toDto(Person personDto);
+}
diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java
new file mode 100644
index 0000000000..bdf8f46a57
--- /dev/null
+++ b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.lombok;
+
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for generation of Lombok Builder Mapper implementations
+ *
+ * @author Eric Martineau
+ */
+class LombokMapperTest {
+
+ @Test
+ void testSimpleImmutableBuilderHappyPath() {
+ PersonDto personDto = PersonMapper.INSTANCE.toDto( new Person( "Bob", 33, new Address( "Wild Drive" ) ) );
+ assertThat( personDto.getAge() ).isEqualTo( 33 );
+ assertThat( personDto.getName() ).isEqualTo( "Bob" );
+ assertThat( personDto.getAddress() ).isNotNull();
+ assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" );
+ }
+
+ @Test
+ void testLombokToImmutable() {
+ Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
+ assertThat( person.getAge() ).isEqualTo( 33 );
+ assertThat( person.getName() ).isEqualTo( "Bob" );
+ assertThat( person.getAddress() ).isNotNull();
+ assertThat( person.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" );
+ }
+}
diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java
index 805662c806..d54c7e6572 100644
--- a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java
+++ b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java
@@ -5,17 +5,17 @@
*/
package org.mapstruct.itest.modules;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.modules.CustomerDto;
import org.mapstruct.itest.modules.CustomerEntity;
import org.mapstruct.itest.modules.CustomerMapper;
-public class ModulesTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ModulesTest {
@Test
- public void shouldMapRecord() {
+ void shouldMapRecord() {
CustomerDto dto = new CustomerDto();
dto.setName( "Kermit" );
dto.setEmail( "kermit@test.com" );
diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java
index d3a45db26b..42cd5084ab 100644
--- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java
+++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java
@@ -5,22 +5,22 @@
*/
package org.mapstruct.itest.naming;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.naming.GolfPlayer;
import org.mapstruct.itest.naming.GolfPlayerDto;
import org.mapstruct.itest.naming.GolfPlayerMapper;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* Test for using a custom naming strategy.
*
* @author Gunnar Morling
*/
-public class NamingTest {
+class NamingTest {
@Test
- public void shouldApplyCustomNamingStrategy() {
+ void shouldApplyCustomNamingStrategy() {
GolfPlayer player = new GolfPlayer()
.withName( "Jared" )
.withHandicap( 9.2D );
diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml
index c8a19228f0..f4cd0b51de 100644
--- a/integrationtest/src/test/resources/pom.xml
+++ b/integrationtest/src/test/resources/pom.xml
@@ -26,8 +26,7 @@
${mapstruct.version}
-
- 1.7.1
+ 6.0.3
@@ -97,43 +96,6 @@
-
- generate-via-processor-plugin
-
- false
-
-
-
-
- org.bsc.maven
- maven-processor-plugin
- 3.3.3
-
-
- process
- generate-sources
-
- process
-
-
-
-
- \${project.build.directory}/generated-sources/mapstruct
-
- org.mapstruct.ap.MappingProcessor
-
-
-
-
- ${project.groupId}
- mapstruct-processor
- ${mapstruct.version}
-
-
-
-
-
- debug-forked-javac
@@ -174,8 +136,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
diff --git a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml
index ecbbf1d6d8..c0d8e2a81b 100644
--- a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml
+++ b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml
@@ -23,7 +23,7 @@
1.6.0
- 0.5.1
+ 0.6.1
@@ -55,10 +55,10 @@
compile-custom
- com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier}
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
grpc-java
- io.grpc:protoc-gen-grpc-java:1.2.0:exe:${os.detected.classifier}
+ io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier}
diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java
index ae3740848f..ac97eba8b4 100644
--- a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java
+++ b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.protobuf;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Christian Bandowski
*/
-public class ProtobufMapperTest {
+class ProtobufMapperTest {
@Test
- public void testSimpleImmutableBuilderHappyPath() {
+ void testSimpleImmutableBuilderHappyPath() {
PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder()
.setAge( 33 )
.setName( "Bob" )
@@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() {
}
@Test
- public void testLombokToImmutable() {
+ void testLombokToImmutable() {
PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) );
assertThat( person.getAge() ).isEqualTo( 33 );
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml
new file mode 100644
index 0000000000..72df10f62c
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ 4.0.0
+
+
+ recordsCrossModuleInterfaceTest
+ org.mapstruct
+ 1.0.0
+
+
+ records-cross-module-1
+
+
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java
new file mode 100644
index 0000000000..ffa53f88b1
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module1;
+
+public interface NestedInterface {
+ String field();
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java
new file mode 100644
index 0000000000..fb23ffe157
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module1;
+
+public interface RootInterface {
+ NestedInterface nested();
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java
new file mode 100644
index 0000000000..6a0ddb86af
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module1;
+
+public record SourceNestedRecord(
+ String field
+) implements NestedInterface {
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java
new file mode 100644
index 0000000000..151ad5208d
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module1;
+
+public record SourceRootRecord(
+ SourceNestedRecord nested
+) implements RootInterface {
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml
new file mode 100644
index 0000000000..5f42efd18e
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ 4.0.0
+
+
+ recordsCrossModuleInterfaceTest
+ org.mapstruct
+ 1.0.0
+
+
+ records-cross-module-2
+
+
+
+
+ org.mapstruct
+ records-cross-module-1
+ 1.0.0
+
+
+
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java
new file mode 100644
index 0000000000..3efba3ec99
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module2;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import org.mapstruct.itest.records.module1.SourceRootRecord;
+
+@Mapper
+public interface RecordInterfaceIssueMapper {
+
+ RecordInterfaceIssueMapper INSTANCE = Mappers.getMapper(RecordInterfaceIssueMapper.class);
+
+ TargetRootRecord map(SourceRootRecord source);
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java
new file mode 100644
index 0000000000..d02a4b58e0
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module2;
+
+public record TargetNestedRecord(
+ String field
+) {
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java
new file mode 100644
index 0000000000..09a69f1bf1
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module2;
+
+public record TargetRootRecord(
+ TargetNestedRecord nested
+) {
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java
new file mode 100644
index 0000000000..7dc5f3b38d
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.module2;
+
+import org.junit.jupiter.api.Test;
+import org.mapstruct.itest.records.module1.SourceNestedRecord;
+import org.mapstruct.itest.records.module1.SourceRootRecord;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordsTest {
+
+ @Test
+ void shouldMap() {
+ SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) );
+ TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.nested() ).isNotNull();
+ assertThat( target.nested().field() ).isEqualTo( "test" );
+ }
+
+}
diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml
new file mode 100644
index 0000000000..120c849dca
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ recordsCrossModuleInterfaceTest
+ pom
+
+
+ module-1
+ module-2
+
+
diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java
similarity index 90%
rename from integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java
rename to integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java
index 2f274792b8..49c73f2f9c 100644
--- a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java
+++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java
@@ -5,17 +5,17 @@
*/
package org.mapstruct.itest.records.mapper;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.records.api.CustomerDto;
import org.mapstruct.itest.records.mapper.CustomerEntity;
import org.mapstruct.itest.records.mapper.CustomerMapper;
-public class RecordsTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordsTest {
@Test
- public void shouldMapRecord() {
+ void shouldMapRecord() {
CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) );
assertThat( customer ).isNotNull();
@@ -24,7 +24,7 @@ public void shouldMapRecord() {
}
@Test
- public void shouldMapIntoRecord() {
+ void shouldMapIntoRecord() {
CustomerEntity entity = new CustomerEntity();
entity.setName( "Kermit" );
entity.setMail( "kermit@test.com" );
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java
new file mode 100644
index 0000000000..9332c47dfb
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class Car {
+
+ private List wheelPositions;
+
+ public List getWheelPositions() {
+ return wheelPositions;
+ }
+
+ public void setWheelPositions(List wheelPositions) {
+ this.wheelPositions = wheelPositions;
+ }
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java
new file mode 100644
index 0000000000..cc69ae7605
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface CarAndWheelMapper {
+
+ CarAndWheelMapper INSTANCE = Mappers.getMapper( CarAndWheelMapper.class );
+
+ default String stringFromWheelPosition(WheelPosition source) {
+ return source == null ? null : source.getPosition();
+ }
+
+ default WheelPosition wheelPositionFromString(String source) {
+ return source == null ? null : new WheelPosition(source);
+ }
+
+ CarDto carDtoFromCar(Car source);
+
+ Car carFromCarDto(CarDto source);
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java
new file mode 100644
index 0000000000..5470550060
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record CarDto(List wheelPositions) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java
new file mode 100644
index 0000000000..b845bdd730
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Documented
+@Target(ElementType.CONSTRUCTOR)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Default {
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java
new file mode 100644
index 0000000000..bf3ce6cd94
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record MemberDto(Boolean isActive, Boolean premium) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java
new file mode 100644
index 0000000000..50a7b1ce00
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class MemberEntity {
+
+ private Boolean isActive;
+ private Boolean premium;
+
+ public Boolean getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(Boolean active) {
+ isActive = active;
+ }
+
+ public Boolean getPremium() {
+ return premium;
+ }
+
+ public void setPremium(Boolean premium) {
+ this.premium = premium;
+ }
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java
new file mode 100644
index 0000000000..3460aeed69
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
+public interface MemberMapper {
+
+ MemberMapper INSTANCE = Mappers.getMapper( MemberMapper.class );
+
+ MemberEntity fromRecord(MemberDto record);
+
+ MemberDto toRecord(MemberEntity entity);
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java
new file mode 100644
index 0000000000..3f990fc89a
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Oliver Erhart
+ */
+public record Task( String id, Long number ) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java
new file mode 100644
index 0000000000..1ba6eb2be9
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import java.util.List;
+
+/**
+ * @author Oliver Erhart
+ */
+public record TaskDto(String id, Long number) {
+
+ @Default
+ TaskDto(String id) {
+ this( id, 1L );
+ }
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java
new file mode 100644
index 0000000000..8d9da767cd
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Oliver Erhart
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface TaskMapper {
+
+ TaskMapper INSTANCE = Mappers.getMapper( TaskMapper.class );
+
+ TaskDto toRecord(Task source);
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java
new file mode 100644
index 0000000000..fe8016ddbf
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class WheelPosition {
+
+ private final String position;
+
+ public WheelPosition(String position) {
+ this.position = position;
+ }
+
+ public String getPosition() {
+ return position;
+ }
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java
new file mode 100644
index 0000000000..fb857e90ca
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.nested;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record Address(String street, String city) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java
new file mode 100644
index 0000000000..a0ce13c0ba
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.nested;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record CareProvider(String externalId, Address address) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java
new file mode 100644
index 0000000000..d7ce7229e9
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.nested;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record CareProviderDto(String id, String street, String city) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java
new file mode 100644
index 0000000000..89ed688976
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.nested;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
+public interface CareProviderMapper {
+
+ CareProviderMapper INSTANCE = Mappers.getMapper( CareProviderMapper.class );
+
+ @Mapping(target = "id", source = "externalId")
+ @Mapping(target = "street", source = "address.street")
+ @Mapping(target = "city", source = "address.city")
+ CareProviderDto map(CareProvider source);
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java
index b404681806..0960c92a45 100644
--- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java
+++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java
@@ -5,17 +5,19 @@
*/
package org.mapstruct.itest.records;
-import static org.assertj.core.api.Assertions.assertThat;
+import java.util.Arrays;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.records.CustomerDto;
import org.mapstruct.itest.records.CustomerEntity;
import org.mapstruct.itest.records.CustomerMapper;
-public class RecordsTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordsTest {
@Test
- public void shouldMapRecord() {
+ void shouldMapRecord() {
CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) );
assertThat( customer ).isNotNull();
@@ -24,7 +26,7 @@ public void shouldMapRecord() {
}
@Test
- public void shouldMapIntoRecord() {
+ void shouldMapIntoRecord() {
CustomerEntity entity = new CustomerEntity();
entity.setName( "Kermit" );
entity.setMail( "kermit@test.com" );
@@ -37,7 +39,7 @@ public void shouldMapIntoRecord() {
}
@Test
- public void shouldMapIntoGenericRecord() {
+ void shouldMapIntoGenericRecord() {
CustomerEntity entity = new CustomerEntity();
entity.setName( "Kermit" );
entity.setMail( "kermit@test.com" );
@@ -47,4 +49,50 @@ public void shouldMapIntoGenericRecord() {
assertThat( value ).isNotNull();
assertThat( value.value() ).isEqualTo( "Kermit" );
}
+
+ @Test
+ void shouldMapIntoRecordWithList() {
+ Car car = new Car();
+ car.setWheelPositions( Arrays.asList( new WheelPosition( "left" ) ) );
+
+ CarDto carDto = CarAndWheelMapper.INSTANCE.carDtoFromCar(car);
+
+ assertThat( carDto ).isNotNull();
+ assertThat( carDto.wheelPositions() )
+ .containsExactly( "left" );
+ }
+
+ @Test
+ void shouldMapMemberRecord() {
+ MemberEntity member = MemberMapper.INSTANCE.fromRecord( new MemberDto( true, false ) );
+
+ assertThat( member ).isNotNull();
+ assertThat( member.getIsActive() ).isTrue();
+ assertThat( member.getPremium() ).isFalse();
+ }
+
+ @Test
+ void shouldMapIntoMemberRecord() {
+ MemberEntity entity = new MemberEntity();
+ entity.setIsActive( false );
+ entity.setPremium( true );
+
+ MemberDto value = MemberMapper.INSTANCE.toRecord( entity );
+
+ assertThat( value ).isNotNull();
+ assertThat( value.isActive() ).isFalse();
+ assertThat( value.premium() ).isTrue();
+ }
+
+ @Test
+ void shouldUseDefaultConstructor() {
+ Task entity = new Task( "some-id", 1000L );
+
+ TaskDto value = TaskMapper.INSTANCE.toRecord( entity );
+
+ assertThat( value ).isNotNull();
+ assertThat( value.id() ).isEqualTo( "some-id" );
+ assertThat( value.number() ).isEqualTo( 1L );
+ }
+
}
diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java
new file mode 100644
index 0000000000..92f074e382
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.records.nested;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class NestedRecordsTest {
+
+ @Test
+ void shouldMapRecord() {
+ CareProvider source = new CareProvider( "kermit", new Address( "Sesame Street", "New York" ) );
+ CareProviderDto target = CareProviderMapper.INSTANCE.map( source );
+
+ assertThat( target ).isNotNull();
+ assertThat( target.id() ).isEqualTo( "kermit" );
+ assertThat( target.street() ).isEqualTo( "Sesame Street" );
+ assertThat( target.city() ).isEqualTo( "New York" );
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/pom.xml b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml
new file mode 100644
index 0000000000..0706425e01
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ sealedSubclassTest
+ jar
+
+
+
+ generate-via-compiler-plugin
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ \${compiler-id}
+ --enable-preview
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-jdt
+ ${org.eclipse.tycho.compiler-jdt.version}
+
+
+
+
+
+
+
+ ${project.groupId}
+ mapstruct-processor
+ ${mapstruct.version}
+ provided
+
+
+
+
+ debug-forked-javac
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+
+ true
+
+ --enable-preview
+ -J-Xdebug
+ -J-Xnoagent
+ -J-Djava.compiler=NONE
+ -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 1
+ --enable-preview
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java
new file mode 100644
index 0000000000..5b68f52e64
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Bike extends Vehicle {
+ private int numberOfGears;
+
+ public int getNumberOfGears() {
+ return numberOfGears;
+ }
+
+ public void setNumberOfGears(int numberOfGears) {
+ this.numberOfGears = numberOfGears;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java
new file mode 100644
index 0000000000..d51e95633b
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class BikeDto extends VehicleDto {
+ private int numberOfGears;
+
+ public int getNumberOfGears() {
+ return numberOfGears;
+ }
+
+ public void setNumberOfGears(int numberOfGears) {
+ this.numberOfGears = numberOfGears;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java
new file mode 100644
index 0000000000..0ed238e2a5
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Car extends Vehicle {
+ private boolean manual;
+
+ public boolean isManual() {
+ return manual;
+ }
+
+ public void setManual(boolean manual) {
+ this.manual = manual;
+ }
+
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java
new file mode 100644
index 0000000000..800bd23d39
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class CarDto extends VehicleDto {
+ private boolean manual;
+
+ public boolean isManual() {
+ return manual;
+ }
+
+ public void setManual(boolean manual) {
+ this.manual = manual;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java
new file mode 100644
index 0000000000..e883c14be3
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Davidson extends Motor {
+ private int numberOfExhausts;
+
+ public int getNumberOfExhausts() {
+ return numberOfExhausts;
+ }
+
+ public void setNumberOfExhausts(int numberOfExhausts) {
+ this.numberOfExhausts = numberOfExhausts;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java
new file mode 100644
index 0000000000..e975226e3e
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class DavidsonDto extends MotorDto {
+ private int numberOfExhausts;
+
+ public int getNumberOfExhausts() {
+ return numberOfExhausts;
+ }
+
+ public void setNumberOfExhausts(int numberOfExhausts) {
+ this.numberOfExhausts = numberOfExhausts;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java
new file mode 100644
index 0000000000..87a48034c6
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class Harley extends Motor {
+ private int engineDb;
+
+ public int getEngineDb() {
+ return engineDb;
+ }
+
+ public void setEngineDb(int engineDb) {
+ this.engineDb = engineDb;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java
new file mode 100644
index 0000000000..2090ee7450
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public final class HarleyDto extends MotorDto {
+ private int engineDb;
+
+ public int getEngineDb() {
+ return engineDb;
+ }
+
+ public void setEngineDb(int engineDb) {
+ this.engineDb = engineDb;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java
new file mode 100644
index 0000000000..fcd5f4e4dc
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public sealed abstract class Motor extends Vehicle permits Harley, Davidson {
+ private int cc;
+
+ public int getCc() {
+ return cc;
+ }
+
+ public void setCc(int cc) {
+ this.cc = cc;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java
new file mode 100644
index 0000000000..bd74eb9296
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public sealed abstract class MotorDto extends VehicleDto permits HarleyDto, DavidsonDto {
+ private int cc;
+
+ public int getCc() {
+ return cc;
+ }
+
+ public void setCc(int cc) {
+ this.cc = cc;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java
new file mode 100644
index 0000000000..b37f623686
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SealedSubclassMapper {
+ SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class );
+
+ VehicleCollectionDto map(VehicleCollection vehicles);
+
+ @SubclassMapping( source = Car.class, target = CarDto.class )
+ @SubclassMapping( source = Bike.class, target = BikeDto.class )
+ @SubclassMapping( source = Harley.class, target = HarleyDto.class )
+ @SubclassMapping( source = Davidson.class, target = DavidsonDto.class )
+ @Mapping( source = "vehicleManufacturingCompany", target = "maker")
+ VehicleDto map(Vehicle vehicle);
+
+ VehicleCollection mapInverse(VehicleCollectionDto vehicles);
+
+ @InheritInverseConfiguration
+ Vehicle mapInverse(VehicleDto dto);
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java
new file mode 100644
index 0000000000..2a4e7560f6
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public abstract sealed class Vehicle permits Bike, Car, Motor {
+ private String name;
+ private String vehicleManufacturingCompany;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVehicleManufacturingCompany() {
+ return vehicleManufacturingCompany;
+ }
+
+ public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) {
+ this.vehicleManufacturingCompany = vehicleManufacturingCompany;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java
new file mode 100644
index 0000000000..1ada92a298
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class VehicleCollection {
+ private Collection vehicles = new ArrayList<>();
+
+ public Collection getVehicles() {
+ return vehicles;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java
new file mode 100644
index 0000000000..0cae412177
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class VehicleCollectionDto {
+ private Collection vehicles = new ArrayList<>();
+
+ public Collection getVehicles() {
+ return vehicles;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java
new file mode 100644
index 0000000000..8c50bdcad9
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+public abstract sealed class VehicleDto permits CarDto, BikeDto, MotorDto {
+ private String name;
+ private String maker;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getMaker() {
+ return maker;
+ }
+
+ public void setMaker(String maker) {
+ this.maker = maker;
+ }
+}
diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java
new file mode 100644
index 0000000000..4fc08b4a33
--- /dev/null
+++ b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.itest.sealedsubclass;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class SealedSubclassTest {
+
+ @Test
+ void mappingIsDoneUsingSubclassMapping() {
+ VehicleCollection vehicles = new VehicleCollection();
+ vehicles.getVehicles().add( new Car() );
+ vehicles.getVehicles().add( new Bike() );
+ vehicles.getVehicles().add( new Harley() );
+ vehicles.getVehicles().add( new Davidson() );
+
+ VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( CarDto.class, BikeDto.class, HarleyDto.class, DavidsonDto.class );
+ }
+
+ @Test
+ void inverseMappingIsDoneUsingSubclassMapping() {
+ VehicleCollectionDto vehicles = new VehicleCollectionDto();
+ vehicles.getVehicles().add( new CarDto() );
+ vehicles.getVehicles().add( new BikeDto() );
+ vehicles.getVehicles().add( new HarleyDto() );
+ vehicles.getVehicles().add( new DavidsonDto() );
+
+ VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles );
+
+ assertThat( result.getVehicles() ).doesNotContainNull();
+ assertThat( result.getVehicles() ) // remove generic so that test works.
+ .extracting( vehicle -> (Class) vehicle.getClass() )
+ .containsExactly( Car.class, Bike.class, Harley.class, Davidson.class );
+ }
+
+ @Test
+ void subclassMappingInheritsInverseMapping() {
+ VehicleCollectionDto vehiclesDto = new VehicleCollectionDto();
+ CarDto carDto = new CarDto();
+ carDto.setMaker( "BenZ" );
+ vehiclesDto.getVehicles().add( carDto );
+
+ VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto );
+
+ assertThat( result.getVehicles() )
+ .extracting( Vehicle::getVehicleManufacturingCompany )
+ .containsExactly( "BenZ" );
+ }
+}
diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetMapper.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetMapper.java
index b3fd443c6b..99f93dab7b 100644
--- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetMapper.java
+++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetMapper.java
@@ -5,10 +5,10 @@
*/
package org.mapstruct.itest.simple;
+import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
-import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.factory.Mappers;
@Mapper(uses = ReferencedCustomMapper.class)
diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java
index f49e09ae32..f9bd8af76b 100644
--- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java
+++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java
@@ -6,6 +6,7 @@
package org.mapstruct.itest.simple;
import java.util.List;
+
import org.mapstruct.itest.simple.SomeType;
public class Target {
diff --git a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java
index 2b6a0bc93b..e248e3a1ad 100644
--- a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java
+++ b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java
@@ -5,17 +5,17 @@
*/
package org.mapstruct.itest.simple;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.simple.Source;
import org.mapstruct.itest.simple.SourceTargetMapper;
import org.mapstruct.itest.simple.Target;
-public class ConversionTest {
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ConversionTest {
@Test
- public void shouldApplyConversions() {
+ void shouldApplyConversions() {
Source source = new Source();
source.setFoo( 42 );
source.setBar( 23L );
@@ -30,18 +30,18 @@ public void shouldApplyConversions() {
}
@Test
- public void shouldHandleNulls() {
+ void shouldHandleNulls() {
Source source = new Source();
Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 1 ) );
- assertThat( target.getBar() ).isEqualTo( 0 );
+ assertThat( target.getBar() ).isZero();
assertThat( target.getZip() ).isEqualTo( "0" );
}
@Test
- public void shouldApplyConversionsToMappedProperties() {
+ void shouldApplyConversionsToMappedProperties() {
Source source = new Source();
source.setQax( 42 );
source.setBaz( 23L );
@@ -54,7 +54,7 @@ public void shouldApplyConversionsToMappedProperties() {
}
@Test
- public void shouldApplyConversionsForReverseMapping() {
+ void shouldApplyConversionsForReverseMapping() {
Target target = new Target();
target.setFoo( 42L );
target.setBar( 23 );
@@ -69,7 +69,7 @@ public void shouldApplyConversionsForReverseMapping() {
}
@Test
- public void shouldApplyConversionsToMappedPropertiesForReverseMapping() {
+ void shouldApplyConversionsToMappedPropertiesForReverseMapping() {
Target target = new Target();
target.setQax( 42 );
target.setBaz( 23L );
@@ -82,7 +82,7 @@ public void shouldApplyConversionsToMappedPropertiesForReverseMapping() {
}
@Test
- public void shouldWorkWithAbstractClass() {
+ void shouldWorkWithAbstractClass() {
Source source = new Source();
source.setFoo( 42 );
source.setBar( 23L );
diff --git a/integrationtest/src/test/resources/springTest/pom.xml b/integrationtest/src/test/resources/springTest/pom.xml
index 3c1fb54552..e4210c0cba 100644
--- a/integrationtest/src/test/resources/springTest/pom.xml
+++ b/integrationtest/src/test/resources/springTest/pom.xml
@@ -18,6 +18,10 @@
springTestjar
+
+
+ ${org.junit6.jupiter.version}
+
diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java
index a020ab2b27..fbb900205b 100644
--- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java
+++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.spring;
+import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
-import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.spring.other.DateMapper;
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = DateMapper.class )
diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java
index 7c04262595..c270594e2a 100644
--- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java
+++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.spring;
+import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
-import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.spring.other.DateMapper;
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = DateMapper.class )
diff --git a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java
index a5b3bb0e65..c0f3de4ce2 100644
--- a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java
+++ b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java
@@ -5,14 +5,14 @@
*/
package org.mapstruct.itest.spring;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.itest.spring.SpringBasedMapperTest.SpringTestConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@@ -22,8 +22,8 @@
* @author Andreas Gudian
*/
@ContextConfiguration(classes = SpringTestConfig.class)
-@RunWith(SpringJUnit4ClassRunner.class)
-public class SpringBasedMapperTest {
+@ExtendWith(SpringExtension.class)
+class SpringBasedMapperTest {
@Configuration
@ComponentScan(basePackageClasses = SpringBasedMapperTest.class)
@@ -40,7 +40,7 @@ public static class SpringTestConfig {
private SecondDecoratedSourceTargetMapper secondDecoratedMapper;
@Test
- public void shouldInjectSpringBasedMapper() {
+ void shouldInjectSpringBasedMapper() {
Source source = new Source();
Target target = mapper.sourceToTarget( source );
@@ -51,7 +51,7 @@ public void shouldInjectSpringBasedMapper() {
}
@Test
- public void shouldInjectDecorator() {
+ void shouldInjectDecorator() {
Source source = new Source();
Target target = decoratedMapper.sourceToTarget( source );
@@ -68,7 +68,7 @@ public void shouldInjectDecorator() {
}
@Test
- public void shouldInjectSecondDecorator() {
+ void shouldInjectSecondDecorator() {
Source source = new Source();
Target target = secondDecoratedMapper.sourceToTarget( source );
diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml
index 1b84638ef2..f122047093 100644
--- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml
+++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
@@ -32,7 +32,6 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1-proc:none
diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/BaseGenerationProcessor.java b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/BaseGenerationProcessor.java
index 01693d7f0e..1147d78083 100644
--- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/BaseGenerationProcessor.java
+++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/BaseGenerationProcessor.java
@@ -8,7 +8,6 @@
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
-
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml
index ee0d556b9f..4c5e4611f0 100644
--- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml
+++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
@@ -38,7 +38,6 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1-XprintProcessorInfo
diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java
index 4a012283d3..d0c76874ae 100644
--- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java
+++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java
@@ -5,23 +5,23 @@
*/
package org.mapstruct.itest.supertypegeneration.usage;
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.mapstruct.itest.supertypegeneration.usage.Order;
import org.mapstruct.itest.supertypegeneration.usage.OrderDto;
import org.mapstruct.itest.supertypegeneration.usage.OrderMapper;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
/**
* Integration test for using MapStruct with another annotation processor that generates super-types of mapping source
* and target types.
*
* @author Gunnar Morling
*/
-public class GeneratedBasesTest {
+class GeneratedBasesTest {
@Test
- public void considersPropertiesOnGeneratedSourceAndTargetTypes() {
+ void considersPropertiesOnGeneratedSourceAndTargetTypes() {
Order order = new Order();
order.setItem( "my item" );
order.setBaseName2( "my base name 2" );
diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml
index bf0d704851..89bd9c2d2f 100644
--- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml
+++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
@@ -32,7 +32,6 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1-proc:none
diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/targettypegeneration/DtoGenerationProcessor.java b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/targettypegeneration/DtoGenerationProcessor.java
index 87dd299bfe..1880055ba2 100644
--- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/targettypegeneration/DtoGenerationProcessor.java
+++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/targettypegeneration/DtoGenerationProcessor.java
@@ -8,7 +8,6 @@
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
-
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml
index da72f667a4..0726580592 100644
--- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml
+++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
@@ -38,7 +38,6 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.1-XprintProcessorInfo
diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java
index d9e3f8d789..b2e30a4797 100644
--- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java
+++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java
@@ -5,9 +5,9 @@
*/
package org.mapstruct.itest.targettypegeneration.usage;
-import static org.junit.Assert.assertEquals;
+import org.junit.jupiter.api.Test;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Integration test for using MapStruct with another annotation processor that generates the target type of a mapping
@@ -15,10 +15,10 @@
*
* @author Gunnar Morling
*/
-public class GeneratedTargetTypeTest {
+class GeneratedTargetTypeTest {
@Test
- public void considersPropertiesOnGeneratedSourceAndTargetTypes() {
+ void considersPropertiesOnGeneratedSourceAndTargetTypes() {
Order order = new Order();
order.setItem( "my item" );
diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml
index 6ac3a01297..24d3436533 100644
--- a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml
+++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.java b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.java
index 2c17279ff8..48b0609d9d 100644
--- a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.java
+++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.java
@@ -8,7 +8,6 @@
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
-
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml
index 79696df47d..d7154c6246 100644
--- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml
+++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml
@@ -21,8 +21,8 @@
- junit
- junit
+ org.junit.jupiter
+ junit-jupitertest
diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java
index e715e66620..55ea60a0ad 100644
--- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java
+++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java
@@ -5,7 +5,7 @@
*/
package org.mapstruct.itest.usestypegeneration.usage;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,10 +14,10 @@
*
* @author Filip Hrisafov
*/
-public class GeneratedUsesTypeTest {
+class GeneratedUsesTypeTest {
@Test
- public void considersPropertiesOnGeneratedSourceAndTargetTypes() {
+ void considersPropertiesOnGeneratedSourceAndTargetTypes() {
Order order = new Order();
order.setItem( "my item" );
diff --git a/jitpack.yml b/jitpack.yml
new file mode 100644
index 0000000000..a21f2c22c4
--- /dev/null
+++ b/jitpack.yml
@@ -0,0 +1,2 @@
+jdk:
+ - openjdk21
diff --git a/mvnw b/mvnw
index 41c0f0c23d..bd8896bf22 100755
--- a/mvnw
+++ b/mvnw
@@ -19,292 +19,277 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven Start Up Batch script
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
-fi
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
- else
- export JAVA_HOME="/Library/Java/Home"
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
fi
fi
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
-fi
-
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
+}
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
+ printf %x\\n $h
+}
- saveddir=`pwd`
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
- M2_HOME=`dirname "$PRG"`/..
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- else
- JAVACMD="`which java`"
- fi
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
fi
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+mkdir -p -- "${MAVEN_HOME%/*}"
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- # end of workaround
- done
- echo "${basedir}"
-}
-
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
fi
-}
-
-BASE_DIR=`find_maven_basedir "$(pwd)"`
-if [ -z "$BASE_DIR" ]; then
- exit 1;
fi
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- fi
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- if [ -n "$MVNW_REPOURL" ]; then
- jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- else
- jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
- if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
- fi
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
- if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget "$jarUrl" -O "$wrapperJarPath"
- else
- wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl -o "$wrapperJarPath" "$jarUrl" -f
- else
- curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
- fi
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaClass=`cygpath --path --windows "$javaClass"`
- fi
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
- fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
- fi
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
-fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
fi
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
-export MAVEN_CMD_LINE_ARGS
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 86115719e5..92450f9327 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -18,165 +19,171 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven Start Up Batch script
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
-
-FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-@REM End of extension
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
-
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
-
-exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/parent/pom.xml b/parent/pom.xml
index bb1229fcb8..b444c1847a 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -11,31 +11,48 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOTpomMapStruct ParentAn annotation processor for generating type-safe bean mappers
- http://mapstruct.org/
+ https://mapstruct.org/2012UTF-8
- 1.0.0.Alpha2
- 3.0.0-M3
- 3.0.0-M5
- 3.1.0
- 5.3.3
+
+ mapstruct/mapstruct
+ /tmp/repository
+ 1.8
+ ${java.version}
+ ${java.version}
+
+ ${git.commit.author.time}
+
+ 1.0.0.Alpha3
+ 3.6.2
+ 3.5.4
+ 3.12.0
+ 7.0.31.6.0
- 8.36.1
- 5.8.0-M1
- 1.4.2
-
+ 13.0.0
+ 5.14.1
+ 2.2.0
+ 1.24.01
- 3.17.2
+ 3.27.7
-
+
jdt_apt
+
+ 1.8
+ 3.25.5
+ 2.3.2
+ 2.3.0
@@ -71,12 +88,12 @@
sonatype-nexus-stagingNexus Release Repository
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
+ https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/sonatype-nexus-snapshotsSonatype Nexus Snapshots
- https://oss.sonatype.org/content/repositories/snapshots/
+ https://central.sonatype.com/repository/maven-snapshots/
@@ -102,7 +119,7 @@
org.freemarkerfreemarker
- 2.3.21
+ 2.3.34org.assertj
@@ -112,7 +129,7 @@
com.google.guavaguava
- 29.0-jre
+ 32.0.0-jreorg.mapstruct.tools.gem
@@ -124,17 +141,24 @@
gem-processor${org.mapstruct.gem.version}
-
- junit
- junit
- 4.13.1
- com.puppycrawl.toolscheckstyle${com.puppycrawl.tools.checkstyle.version}
+
+ org.jetbrains.kotlin
+ kotlin-bom
+ ${kotlin.version}
+ pom
+ import
+
+
+ org.jetbrains.kotlin
+ kotlin-metadata-jvm
+ ${kotlin.version}
+ org.junitjunit-bom
@@ -152,29 +176,40 @@
javax.enterprisecdi-api
- 1.2
+ 2.0.SP1
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ 4.0.1javax.injectjavax.inject1
+
+ jakarta.inject
+ jakarta.inject-api
+ 2.0.1
+ org.jboss.arquillianarquillian-bom
- 1.0.2.Final
+ 1.7.2.Finalimportpomorg.jboss.arquillian.containerarquillian-weld-se-embedded-1.1
- 1.0.0.CR7
+ 1.0.0.Finalorg.jboss.weld
- weld-core
- 2.3.2.Final
+ weld-core-impl
+ 3.1.8.Final
+ testorg.glassfish
@@ -208,7 +243,7 @@
org.projectlomboklombok
- 1.18.20
+ 1.18.38org.immutables
@@ -223,7 +258,7 @@
com.google.protobufprotobuf-java
- 3.6.0
+ ${protobuf.version}org.inferred
@@ -231,11 +266,42 @@
1.14.6
+
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
joda-timejoda-time
- 2.9
+ 2.12.5
+
+
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ ${jaxb-runtime.version}
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ 3.0.1
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ 3.0.2
@@ -262,12 +328,12 @@
org.codehaus.plexusplexus-utils
- 3.0.20
+ 3.0.24commons-iocommons-io
- 2.7
+ 2.15.0
@@ -307,12 +373,12 @@
org.apache.maven.pluginsmaven-assembly-plugin
- 3.1.1
+ 3.8.0org.apache.maven.pluginsmaven-checkstyle-plugin
- 3.1.1
+ 3.6.0build-config/checkstyle.xmltrue
@@ -344,35 +410,26 @@
org.apache.maven.pluginsmaven-clean-plugin
- 3.1.0
+ 3.5.0org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.1
-
- 1.8
- 1.8
-
+ 3.15.0org.apache.maven.pluginsmaven-dependency-plugin
- 3.1.1
+ 3.9.0org.apache.maven.pluginsmaven-deploy-plugin
- 3.0.0-M1
+ 3.1.4true
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.6
- org.apache.maven.pluginsmaven-enforcer-plugin
@@ -402,12 +459,12 @@
org.apache.maven.pluginsmaven-install-plugin
- 3.0.0-M1
+ 3.1.4org.apache.maven.pluginsmaven-jar-plugin
- 3.1.1
+ 3.5.0org.apache.maven.plugins
@@ -419,35 +476,20 @@
8
-
- org.apache.maven.plugins
- maven-release-plugin
- 2.5.3
-
- -DskipTests ${add.release.arguments}
- clean install
- false
- true
- @{project.version}
- true
- false
- release
-
- org.apache.maven.pluginsmaven-resources-plugin
- 3.1.0
+ 3.4.0org.apache.maven.pluginsmaven-source-plugin
- 3.0.1
+ 3.4.0org.apache.maven.pluginsmaven-site-plugin
- 3.7.1
+ 3.21.0org.apache.maven.plugins
@@ -461,25 +503,35 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.2.0
+ 3.6.1
- com.mycila.maven-license-plugin
- maven-license-plugin
- 1.10.b1
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ com.mycila
+ license-maven-plugin
+ 4.6org.codehaus.mojoanimal-sniffer-maven-plugin
- 1.17
+ 1.20org.ow2.asmasm
- 6.2.1
+ 7.0
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.16.2
+ org.eclipse.m2elifecycle-mapping
@@ -534,7 +586,7 @@
org.jacocojacoco-maven-plugin
- 0.8.6
+ 0.8.14org.jvnet.jaxb2.maven2
@@ -559,7 +611,7 @@
com.github.siom79.japicmpjapicmp-maven-plugin
- 0.15.2
+ 0.25.4verify
@@ -599,38 +651,43 @@
- com.mycila.maven-license-plugin
- maven-license-plugin
+ com.mycila
+ license-maven-plugin
- ${basedir}/../etc/license.txttrue
-
- **/.idea/**
- **/.mvn/**
- **/build-config/checkstyle.xml
- **/build-config/import-control.xml
- copyright.txt
- **/LICENSE.txt
- **/mapstruct.xml
- **/ci-settings.xml
- **/eclipse-formatter-config.xml
- **/forbidden-apis.txt
- **/checkstyle-for-generated-sources.xml
- **/nb-configuration.xml
- **/junit-platform.properties
- maven-settings.xml
- readme.md
- CONTRIBUTING.md
- .gitattributes
- .gitignore
- .factorypath
- .checkstyle
- *.yml
- mvnw*
- **/*.asciidoc
- **/binding.xjb
- **/*.flattened-pom.xml
-
+
+
+ ${basedir}/../etc/license.txt
+
+ **/.idea/**
+ **/.mvn/**
+ **/build-config/checkstyle.xml
+ **/build-config/import-control.xml
+ copyright.txt
+ **/LICENSE.txt
+ **/mapstruct.xml
+ **/ci-settings.xml
+ **/eclipse-formatter-config.xml
+ **/forbidden-apis.txt
+ **/checkstyle-for-generated-sources.xml
+ **/nb-configuration.xml
+ **/junit-platform.properties
+ maven-settings.xml
+ readme.md
+ CONTRIBUTING.md
+ NEXT_RELEASE_CHANGELOG.md
+ .gitattributes
+ .gitignore
+ .factorypath
+ .checkstyle
+ *.yml
+ mvnw*
+ **/*.asciidoc
+ **/binding.xjb
+ **/*.flattened-pom.xml
+
+
+ SLASHSTAR_STYLESLASHSTAR_STYLE
@@ -660,6 +717,9 @@
java181.0
+
+ org.mapstruct.ap.internal.util.IgnoreJRERequirement
+
@@ -679,7 +739,7 @@
- [1.8,)
+ [${minimum.java.version},)
@@ -746,12 +806,21 @@
+
+ org.codehaus.mojo
+ versions-maven-plugin
+
- release
+ publication
+
+
+ release
+
+
@@ -778,18 +847,94 @@
+
+
+
+
+ stage
+
+ local::file:${maven.multiModuleProjectDirectory}/target/staging-deploy
+
+
+ deploy
+
+
+
+ jreleaser
+
+
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
+ org.jreleaser
+ jreleaser-maven-plugin
+ ${jreleaser.plugin.version}
+
+ true
+
+
+ Mapstruct
+
+ https://mapstruct.org/
+ https://mapstruct.org/documentation/stable/reference/html/
+
+
+
+ ALWAYS
+
+ ALWAYS
+ true
+
+
+
+ false
+
+
+
+
+
+ RELEASE
+ https://central.sonatype.com/api/v1/publisher
+ ${maven.multiModuleProjectDirectory}/target/staging-deploy
+
+
+ org.mapstruct
+ mapstruct-jdk8
+ false
+ false
+
+
+
+
+
+
+
+
+ {{projectVersion}}
+ {{projectVersion}}
+
+ ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md
+
+
+ \d+\.\d+\.\d+\.(Alpha|Beta|M|RC)\d*
+
+
+ legacy
+
+
+
+
+
+ ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.tar.gz
+
+
+ ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.zip
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 28c35aea74..c753e3c1e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOTparent/pom.xml
@@ -27,7 +27,6 @@
corecore-jdk8processor
- integrationtesttrue
@@ -36,10 +35,14 @@
- com.mycila.maven-license-plugin
- maven-license-plugin
+ com.mycila
+ license-maven-plugin
- etc/license.txt
+
+
+ etc/license.txt
+
+ XML_STYLESLASHSTAR_STYLE
@@ -71,5 +74,17 @@
distribution
+
+ test
+
+
+ release
+ !true
+
+
+
+ integrationtest
+
+
diff --git a/processor/pom.xml b/processor/pom.xml
index fcc6dbbab8..e317365aec 100644
--- a/processor/pom.xml
+++ b/processor/pom.xml
@@ -12,7 +12,7 @@
org.mapstructmapstruct-parent
- 1.5.0-SNAPSHOT
+ 1.7.0-SNAPSHOT../parent/pom.xml
@@ -24,6 +24,7 @@
+ 21
@@ -41,11 +42,20 @@
mapstructprovided
+
+ org.jetbrains.kotlin
+ kotlin-metadata-jvm
+ provided
+
+
+ org.jetbrains.kotlin
+ kotlin-compiler-embeddable
+ test
+ org.eclipse.tychotycho-compiler-jdt
- provided
- true
+ test
@@ -84,6 +94,16 @@
javax.injecttest
+
+ jakarta.inject
+ jakarta.inject-api
+ test
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ test
+
@@ -127,6 +147,20 @@
joda-timetest
+
+
+
+ org.jspecify
+ jspecify
+ test
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ provided
+ true
+
@@ -145,6 +179,13 @@
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+ all,-missing
+
+ org.apache.maven.pluginsmaven-surefire-plugin
@@ -265,10 +306,49 @@
+
+ org.jetbrains.kotlin
+
+ kotlin-maven-plugin
+
+ false
+
+
+
+ kotlin-compile
+ compile
+
+ compile
+
+
+ ${minimum.java.version}
+
+ src/main/kotlin
+
+ src/main/java
+
+
+
+
+ kotlin-test-compile
+ test-compile
+
+ test-compile
+
+
+ ${minimum.java.version}
+
+ src/test/java
+
+
+
+
+ org.apache.maven.pluginsmaven-compiler-plugin
+ ${minimum.java.version}org.mapstruct.tools.gem
@@ -278,6 +358,33 @@
+
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+ org.apache.maven.plugins
@@ -365,7 +472,6 @@
javax.xml.bindjaxb-api
- 2.3.1providedtrue
diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
index e7e52be695..50831a8f42 100644
--- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
@@ -17,12 +17,12 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
@@ -30,19 +30,21 @@
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementKindVisitor6;
+import javax.lang.model.util.ElementKindVisitor8;
import javax.tools.Diagnostic.Kind;
+import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.model.Mapper;
+import org.mapstruct.ap.internal.option.MappingOption;
import org.mapstruct.ap.internal.option.Options;
-import org.mapstruct.ap.internal.gem.MapperGem;
-import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext;
import org.mapstruct.ap.internal.processor.ModelElementProcessor;
import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext;
import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.AnnotationProcessorContext;
import org.mapstruct.ap.internal.util.RoundContext;
+import org.mapstruct.ap.internal.util.Services;
+import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import static javax.lang.model.element.ElementKind.CLASS;
@@ -67,9 +69,9 @@
*
if no error occurred, write out the model into Java source files
*
*
- * For reading annotation attributes, gems as generated with help of the Hickory tool are used. These gems allow a comfortable access to
- * annotations and their attributes without depending on their class objects.
+ * For reading annotation attributes, gems as generated with help of Gem Tools. These gems allow comfortable access to annotations and
+ * their attributes without depending on their class objects.
*
* The creation of Java source files is done using the FreeMarker template engine.
* Each node of the mapper model has a corresponding FreeMarker template file which provides the Java representation of
@@ -80,14 +82,6 @@
* @author Gunnar Morling
*/
@SupportedAnnotationTypes("org.mapstruct.Mapper")
-@SupportedOptions({
- MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,
- MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT,
- MappingProcessor.UNMAPPED_TARGET_POLICY,
- MappingProcessor.DEFAULT_COMPONENT_MODEL,
- MappingProcessor.DEFAULT_INJECTION_STRATEGY,
- MappingProcessor.VERBOSE
-})
public class MappingProcessor extends AbstractProcessor {
/**
@@ -95,14 +89,35 @@ public class MappingProcessor extends AbstractProcessor {
*/
private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false;
- protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp";
- protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT =
- "mapstruct.suppressGeneratorVersionInfoComment";
- protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy";
- protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel";
- protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy";
- protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile";
- protected static final String VERBOSE = "mapstruct.verbose";
+ // CHECKSTYLE:OFF
+ // Deprecated options, kept for backwards compatibility.
+ // They will be removed in a future release.
+ @Deprecated
+ protected static final String SUPPRESS_GENERATOR_TIMESTAMP = MappingOption.SUPPRESS_GENERATOR_TIMESTAMP.getOptionName();
+ @Deprecated
+ protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT.getOptionName();
+ @Deprecated
+ protected static final String UNMAPPED_TARGET_POLICY = MappingOption.UNMAPPED_TARGET_POLICY.getOptionName();
+ @Deprecated
+ protected static final String UNMAPPED_SOURCE_POLICY = MappingOption.UNMAPPED_SOURCE_POLICY.getOptionName();
+ @Deprecated
+ protected static final String DEFAULT_COMPONENT_MODEL = MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName();
+ @Deprecated
+ protected static final String DEFAULT_INJECTION_STRATEGY = MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName();
+ @Deprecated
+ protected static final String ALWAYS_GENERATE_SERVICE_FILE = MappingOption.ALWAYS_GENERATE_SERVICE_FILE.getOptionName();
+ @Deprecated
+ protected static final String DISABLE_BUILDERS = MappingOption.DISABLE_BUILDERS.getOptionName();
+ @Deprecated
+ protected static final String VERBOSE = MappingOption.VERBOSE.getOptionName();
+ @Deprecated
+ protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY.getOptionName();
+ @Deprecated
+ protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY.getOptionName();
+ // CHECKSTYLE:ON
+
+ private final Set additionalSupportedOptions;
+ private final String additionalSupportedOptionsError;
private Options options;
@@ -115,35 +130,43 @@ public class MappingProcessor extends AbstractProcessor {
*
* If the hierarchy of a mapper's source/target types is never completed (i.e. the missing super-types are not
* generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error
- * due to the inconsistent Java types used as source or target anyways.
+ * due to the inconsistent Java types used as source or target anyway.
*/
private Set deferredMappers = new HashSet<>();
+ public MappingProcessor() {
+ Set additionalSupportedOptions;
+ String additionalSupportedOptionsError;
+ try {
+ additionalSupportedOptions = resolveAdditionalSupportedOptions();
+ additionalSupportedOptionsError = null;
+ }
+ catch ( IllegalStateException ex ) {
+ additionalSupportedOptions = Collections.emptySet();
+ additionalSupportedOptionsError = ex.getMessage();
+ }
+ this.additionalSupportedOptions = additionalSupportedOptions;
+ this.additionalSupportedOptionsError = additionalSupportedOptionsError;
+ }
+
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init( processingEnv );
- options = createOptions();
+ options = new Options( processingEnv.getOptions() );
annotationProcessorContext = new AnnotationProcessorContext(
processingEnv.getElementUtils(),
processingEnv.getTypeUtils(),
processingEnv.getMessager(),
- options.isVerbose()
+ options.isDisableBuilders(),
+ options.isDisableJSpecify(),
+ options.isVerbose(),
+ resolveAdditionalOptions( processingEnv.getOptions() )
);
- }
- private Options createOptions() {
- String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY );
-
- return new Options(
- Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
- Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ),
- unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null,
- processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ),
- processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ),
- Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
- Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) )
- );
+ if ( additionalSupportedOptionsError != null ) {
+ processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError );
+ }
}
@Override
@@ -204,6 +227,15 @@ else if ( !deferredMappers.isEmpty() ) {
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
}
+ @Override
+ public Set getSupportedOptions() {
+ return Stream.concat(
+ Stream.of( MappingOption.values() ).map( MappingOption::getOptionName ),
+ additionalSupportedOptions.stream()
+ )
+ .collect( Collectors.toSet() );
+ }
+
/**
* Gets fresh copies of all mappers deferred from previous rounds (the originals may contain references to
* erroneous source/target type elements).
@@ -233,7 +265,7 @@ private Set getMappers(final Set extends TypeElement> annotations
try {
Set extends Element> annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation );
- for (Element mapperElement : annotatedMappers) {
+ for ( Element mapperElement : annotatedMappers ) {
TypeElement mapperTypeElement = asTypeElement( mapperElement );
// on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with
@@ -261,7 +293,11 @@ private void processMapperElements(Set mapperElements, RoundContext
// of one outer interface
List extends Element> tst = mapperElement.getEnclosedElements();
ProcessorContext context = new DefaultModelElementProcessorContext(
- processingEnv, options, roundContext, getDeclaredTypesNotToBeImported( mapperElement )
+ processingEnv,
+ options,
+ roundContext,
+ getDeclaredTypesNotToBeImported( mapperElement ),
+ mapperElement
);
processMapperTypeElement( context, mapperElement );
@@ -340,7 +376,7 @@ private
R process(ProcessorContext context, ModelElementProcessor
p
/**
* Retrieves all model element processors, ordered by their priority value
- * (with the method retrieval processor having the lowest priority value (1)
+ * (with the method retrieval processor having the lowest priority value (1))
* and the code generation processor the highest priority value.
*
* @return A list with all model element processors.
@@ -360,14 +396,14 @@ private
R process(ProcessorContext context, ModelElementProcessor
p
processors.add( processorIterator.next() );
}
- Collections.sort( processors, new ProcessorComparator() );
+ processors.sort( new ProcessorComparator() );
return processors;
}
private TypeElement asTypeElement(Element element) {
return element.accept(
- new ElementKindVisitor6() {
+ new ElementKindVisitor8() {
@Override
public TypeElement visitTypeAsInterface(TypeElement e, Void p) {
return e;
@@ -382,6 +418,35 @@ public TypeElement visitTypeAsClass(TypeElement e, Void p) {
);
}
+ /**
+ * Fetch the additional supported options provided by the SPI {@link AdditionalSupportedOptionsProvider}.
+ *
+ * @return the additional supported options
+ */
+ private static Set resolveAdditionalSupportedOptions() {
+ Set additionalSupportedOptions = null;
+ for ( AdditionalSupportedOptionsProvider optionsProvider :
+ Services.all( AdditionalSupportedOptionsProvider.class ) ) {
+ if ( additionalSupportedOptions == null ) {
+ additionalSupportedOptions = new HashSet<>();
+ }
+ Set providerOptions = optionsProvider.getAdditionalSupportedOptions();
+
+ for ( String providerOption : providerOptions ) {
+ // Ensure additional options are not in the mapstruct namespace
+ if ( providerOption.startsWith( "mapstruct" ) ) {
+ throw new IllegalStateException(
+ "Additional SPI options cannot start with \"mapstruct\". Provider " + optionsProvider +
+ " provided option " + providerOption );
+ }
+ additionalSupportedOptions.add( providerOption );
+ }
+
+ }
+
+ return additionalSupportedOptions == null ? Collections.emptySet() : additionalSupportedOptions;
+ }
+
private static class ProcessorComparator implements Comparator> {
@Override
@@ -400,4 +465,16 @@ private DeferredMapper(TypeElement deferredMapperElement, Element erroneousEleme
this.erroneousElement = erroneousElement;
}
}
+
+ /**
+ * Filters only the options belonging to the declared additional supported options.
+ *
+ * @param options all processor environment options
+ * @return filtered options
+ */
+ private Map resolveAdditionalOptions(Map options) {
+ return options.entrySet().stream()
+ .filter( entry -> additionalSupportedOptions.contains( entry.getKey() ) )
+ .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java
index 2f530618c9..3503a0e274 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java
@@ -21,15 +21,15 @@
*
*
* In general each type comes with a "parse" method to convert a string to this particular type.
- * For formatting a dedicated instance of {@link java.time.format.DateTimeFormatter} is used.
+ * For formatting a dedicated instance of {@link DateTimeFormatter} is used.
*
*
* If no date format for mapping is specified predefined ISO* formatters from
- * {@link java.time.format.DateTimeFormatter} are used.
+ * {@link DateTimeFormatter} are used.
*
*
- * An overview of date and time types shipped with Java 8 can be found at
- * http://docs.oracle.com/javase/tutorial/datetime/iso/index.html.
+ * An overview of date and time types shipped with Java 8 can be found at the
+ * Standard Calendar Tutorial
*