diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000000..e3b6c8ff8f --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + patch: off + project: + default: + # basic + threshold: 0.05 +comment: off 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 new file mode 100644 index 0000000000..04897b2156 --- /dev/null +++ b/.github/workflows/java-ea.yml @@ -0,0 +1,24 @@ +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: + name: 'Linux JDK EA' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: 'Set up JDK' + uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0 + with: + website: jdk.java.net + release: EA + - name: 'Test' + 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 new file mode 100644 index 0000000000..d1ebef4642 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,66 @@ +name: CI + +on: [push, pull_request] + +permissions: + contents: read + +env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + test_jdk: + strategy: + fail-fast: false + matrix: + java: [21, 25, 26] + name: 'Linux JDK ${{ matrix.java }}' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: 'Set up JDK' + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + - name: 'Test' + 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' + 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: 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 + integration_test_jdk: + strategy: + fail-fast: false + matrix: + java: [ 8, 17 ] + name: 'Linux JDK ${{ matrix.java }}' + runs-on: ubuntu-latest + steps: + - 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: + 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: + 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} @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -33,15 +20,7 @@ jar MapStruct Build Configuration - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - + + true + diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index 1f371acd2b..b15e108c5c 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -15,6 +15,8 @@ --> + + - + + + + + + + + + + @@ -60,10 +71,6 @@ - - - - @@ -113,14 +120,18 @@ + + + + + + + - - - - @@ -138,12 +149,14 @@ - + - + + + @@ -202,9 +215,9 @@ + - diff --git a/build-config/src/main/resources/build-config/import-control.xml b/build-config/src/main/resources/build-config/import-control.xml index 7e04d49ce7..6c8056bd8d 100644 --- a/build-config/src/main/resources/build-config/import-control.xml +++ b/build-config/src/main/resources/build-config/import-control.xml @@ -10,8 +10,8 @@ - - + + diff --git a/copyright.txt b/copyright.txt index 01b7b4dc96..74f18d89b1 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,30 +1,95 @@ Contributors ============ -Andreas Gudian -Christian Schuster -Christophe Labouisse -Ciaran Liedeman -Dilip Krishnan -Ewald Volkert -Filip Hrisafov -Gunnar Morling -Ivo Smid -Markus Heberling -Michael Pardo -Mustafa Caylak -Oliver Ehrenmüller -Paul Strugnell -Pascal Grün -Pavel Makhov -Peter Larson -Remko Plantenga -Remo Meier -Samuel Wright -Sebastian Hasait -Sean Huang -Sjaak Derksen -Stefan May -Timo Eckhardt -Tomek Gubala -Vincent Alexander Beelte +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 +Dilip Krishnan - https://github.com/dilipkrish +Dmytro Polovinkin - https://github.com/navpil +Ewald Volkert - https://github.com/eforest +Eric Martineau - https://github.com/ericmartineau +Ewald Volkert - https://github.com/eforest +Filip Hrisafov - https://github.com/filiphr +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-common/pom.xml b/core-common/pom.xml deleted file mode 100644 index e926da6b0c..0000000000 --- a/core-common/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - 4.0.0 - - - org.mapstruct - mapstruct-parent - 1.2.0-SNAPSHOT - ../parent/pom.xml - - - mapstruct-common - jar - MapStruct Core Common - - - true - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - diff --git a/core-common/src/main/java/org/mapstruct/AfterMapping.java b/core-common/src/main/java/org/mapstruct/AfterMapping.java deleted file mode 100644 index 9f7dfb9b63..0000000000 --- a/core-common/src/main/java/org/mapstruct/AfterMapping.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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; - -/** - * Marks a method to be invoked at the end of a generated mapping method, right before the last {@code return} statement - * of the mapping method. The method can be implemented in an abstract mapper class or be declared in a type (class or - * interface) referenced in {@link Mapper#uses()} in order to be used in a mapping method. - *

- * Only methods with return type {@code void} may be annotated with this annotation. - *

- * If the method has parameters, the method invocation is only generated if all parameters can be assigned by - * the source or target parameters of the mapping method: - *

    - *
  • A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping. - *
  • - *
  • A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.
  • - *
  • Any other parameter is populated with a source parameter of the mapping, whereas each source parameter is used - * once at most.
  • - *
- *

- * All after-mapping methods that can be applied to a mapping method will be used. Their order is determined by - * their location of definition: - *

    - *
  • The order of methods within one type can not be guaranteed, as it depends on the compiler and the processing - * environment implementation.
  • - *
  • Methods declared in one type are used after methods declared in their super-type.
  • - *
  • Methods implemented in the mapper itself are used before methods from types referenced in {@link Mapper#uses()}. - *
  • - *
  • Types referenced in {@link Mapper#uses()} are searched for after-mapping methods in the order specified - * in the annotation.
  • - *
- *

- * Example: - * - *

- * 
- * @AfterMapping
- * public void calledWithoutArgs() {
- *     // ...
- * }
- *
- * @AfterMapping
- * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
- *     // ...
- * }
- *
- * @AfterMapping
- * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
- *     // ...
- * }
- *
- * public abstract TargetDto toTargetDto(SourceEntity source);
- *
- * // generates:
- *
- * public TargetDto toTargetDto(SourceEntity source) {
- *     if ( source == null ) {
- *         return null;
- *     }
- *
- *     TargetDto targetDto = new TargetDto();
- *
- *     // actual mapping code
- *
- *     calledWithoutArgs();
- *     calledWithSourceAndTargetType( source, TargetDto.class );
- *     calledWithSourceAndTarget( source, targetDto );
- *
- *     return targetDto;
- * }
- * 
- * 
- * - * @author Andreas Gudian - * @see BeforeMapping - */ -@Experimental -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface AfterMapping { - -} diff --git a/core-common/src/main/java/org/mapstruct/BeanMapping.java b/core-common/src/main/java/org/mapstruct/BeanMapping.java deleted file mode 100644 index 57d5b1833b..0000000000 --- a/core-common/src/main/java/org/mapstruct/BeanMapping.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Configures the mapping between two bean types. - *

- * Either {@link #resultType()} , {@link #qualifiedBy()} or {@link #nullValueMappingStrategy()} must be specified. - *

- * - * @author Sjaak Derksen - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface BeanMapping { - - /** - * Specifies the result type of the factory method to be used in case several factory methods qualify. - * - * @return the resultType to select - */ - Class resultType() default void.class; - - /** - * A qualifier can be specified to aid the selection process of a suitable factory method. This is useful in case - * multiple factory method (hand written of internal) qualify and result in an 'Ambiguous factory methods' error. - * - * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. - * - * @return the qualifiers - */ - Class[] qualifiedBy() default { }; - - /** - * See: { @link #qualifiedBy() }. String form of a predefined { @link @Qualifier }. The { @link @Qualifier } - * is more verbose, but offers more flexibility in terms of for instance refactoring. At the other hand, there - * is no need to define own annotations. - * - * @return the qualifiers - */ - String[] qualifiedByName() default { }; - - /** - * The strategy to be applied when {@code null} is passed as source value to this bean mapping. If no - * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or - * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} - * by default. - * - * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. - */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; -} diff --git a/core-common/src/main/java/org/mapstruct/BeforeMapping.java b/core-common/src/main/java/org/mapstruct/BeforeMapping.java deleted file mode 100644 index 7e3ced1c34..0000000000 --- a/core-common/src/main/java/org/mapstruct/BeforeMapping.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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; - -/** - * Marks a method to be invoked at the beginning of a generated mapping method. The method can be implemented in an - * abstract mapper class or be declared in a type (class or interface) referenced in {@link Mapper#uses()} in order to - * be used in a mapping method. - *

- * Only methods with return type {@code void} may be annotated with this annotation. - *

- * If the method has parameters, the method invocation is only generated if all parameters can be assigned by - * the source or target parameters of the mapping method: - *

    - *
  • A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping. - *
  • - *
  • A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.
  • - *
  • Any other parameter is populated with a source parameter of the mapping, whereas each source parameter is used - * once at most.
  • - *
- * If a before-mapping method does not contain a {@code @}{@link MappingTarget} parameter, it is invoked - * directly at the beginning of the applicable mapping method. If it contains a {@code @}{@link MappingTarget} - * parameter, the method is invoked after the target parameter has been initialized in the mapping method. - *

- * All before-mapping methods that can be applied to a mapping method will be used. Their order is determined - * by their location of definition: - *

    - *
  • The order of methods within one type can not be guaranteed, as it depends on the compiler and the processing - * environment implementation.
  • - *
  • Methods declared in one type are used after methods declared in their super-type.
  • - *
  • Methods implemented in the mapper itself are used before methods from types referenced in {@link Mapper#uses()}. - *
  • - *
  • Types referenced in {@link Mapper#uses()} are searched for after-mapping methods in the order specified - * in the annotation.
  • - *
- *

- * Example: - * - *

- * 
- * @BeforeMapping
- * public void calledWithoutArgs() {
- *     // ...
- * }
- *
- * @BeforeMapping
- * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
- *     // ...
- * }
- *
- * @BeforeMapping
- * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
- *     // ...
- * }
- *
- * public abstract TargetDto toTargetDto(SourceEntity source);
- *
- * // generates:
- *
- * public TargetDto toTargetDto(SourceEntity source) {
- *     calledWithoutArgs();
- *     calledWithSourceAndTargetType( source, TargetDto.class );
- *
- *     if ( source == null ) {
- *         return null;
- *     }
- *
- *     TargetDto targetDto = new TargetDto();
- *
- *     calledWithSourceAndTarget( source, targetDto );
- *
- *     // actual mapping code
- *
- *     return targetDto;
- * }
- * 
- * 
- * - * @author Andreas Gudian - * @see AfterMapping - */ -@Experimental -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface BeforeMapping { - -} diff --git a/core-common/src/main/java/org/mapstruct/CollectionMappingStrategy.java b/core-common/src/main/java/org/mapstruct/CollectionMappingStrategy.java deleted file mode 100644 index d740d76fc3..0000000000 --- a/core-common/src/main/java/org/mapstruct/CollectionMappingStrategy.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -/** - * Strategy for propagating the value of collection-typed properties from source to target. - * - * @author Sjaak Derksen - */ -public enum CollectionMappingStrategy { - - /** - * The setter of the target property will be used to propagate the value: - * {@code orderDto.setOrderLines(order.getOrderLines)}. - *

- * If no setter is available but a getter method, this will be used, under the assumption it has been initialized: - * {@code orderDto.getOrderLines().addAll(order.getOrderLines)}. - */ - ACCESSOR_ONLY, - - /** - * If present, the setter of the target property will be used to propagate the value: - * {@code orderDto.setOrderLines(order.getOrderLines)}. - *

- * If no setter but and adder method is present, that adder will be invoked for each element of the source - * collection: {@code order.addOrderLine(orderLine() )}. - *

- * If neither a setter nor an adder method but a getter for the target property is present, that getter will be - * used, assuming it returns an initialized collection: If no setter is available, MapStruct will first look for an - * adder method before resorting to a getter. - */ - SETTER_PREFERRED, - - /** - * Identical to {@link #SETTER_PREFERRED}, only that adder methods will be preferred over setter methods, if both - * are present for a given collection-typed property. - */ - ADDER_PREFERRED; -} diff --git a/core-common/src/main/java/org/mapstruct/InheritConfiguration.java b/core-common/src/main/java/org/mapstruct/InheritConfiguration.java deleted file mode 100644 index deca617601..0000000000 --- a/core-common/src/main/java/org/mapstruct/InheritConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Advises the code generator to apply the configuration (as given via {@link Mapping}, {@link IterableMapping} etc.) - * from another mapping method (declared on the same mapper type) or prototype method (declared on a mapper config class - * referenced via {@link Mapper#config()}) to the annotated method as well. - *

- * If no method can be identified unambiguously as configuration source (i.e. several candidate methods with matching - * source and target type exist), the name of the method to inherit from must be specified via {@link #name()}. - *

- * A typical use case is annotating an update method so it inherits all mappings from a corresponding "standard" mapping - * method: - * - *

- * 
- * @Mappings({
- *     @Mapping(target="make", source="brand"),
- *     @Mapping(target="seatCount", source="numberOfSeats")
- * })
- * CarDto carToCarDto(Car car);
- *
- * @InheritConfiguration
- * void updateCarDto(Car car, @MappingTarget CarDto carDto);
- * 
- * 
- * - * @author Sjaak Derksen - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface InheritConfiguration { - - /** - * The name of the mapping method to inherit the mappings from. Needs only to be specified in case more than one - * method with matching source and target type exists. - * - * @return The name of the mapping method to inherit the mappings from. - */ - String name() default ""; -} diff --git a/core-common/src/main/java/org/mapstruct/InheritInverseConfiguration.java b/core-common/src/main/java/org/mapstruct/InheritInverseConfiguration.java deleted file mode 100644 index 64cb250e46..0000000000 --- a/core-common/src/main/java/org/mapstruct/InheritInverseConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Advises the code generator to apply all the {@link Mapping}s from an inverse mapping method to the annotated method - * as well. An inverse mapping method is a method which has the annotated method's source type as target type (return - * type or indicated through a parameter annotated with {@link MappingTarget}) and the annotated method's target type as - * source type. - *

- * Any mappings given on the annotated method itself are added to those mappings inherited from the inverse method. In - * case of a conflict local mappings take precedence over inherited mappings. - *

- * If more than one matching inverse method exists, the name of the method to inherit the configuration from must be - * specified via {@link #name()} - * - * @author Sjaak Derksen - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -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. - * - * @return The name of the inverse mapping method to inherit the mappings from. - */ - String name() default ""; -} diff --git a/core-common/src/main/java/org/mapstruct/IterableMapping.java b/core-common/src/main/java/org/mapstruct/IterableMapping.java deleted file mode 100644 index c8a8f69736..0000000000 --- a/core-common/src/main/java/org/mapstruct/IterableMapping.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.text.SimpleDateFormat; -import java.text.DecimalFormat; -import java.util.Date; - -/** - * Configures the mapping between two iterable types, e.g. {@code List} and {@code List}. - * - *

Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy - * must be specified

- * - * @author Gunnar Morling - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface IterableMapping { - - /** - * 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. - * - * @return A date format string as processable by {@link SimpleDateFormat}. - */ - 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. - * - * @return A decimal format string as processable by {@link DecimalFormat}. - */ - String numberFormat() 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. - * - * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. - * - * @return the qualifiers - */ - Class[] qualifiedBy() default { }; - - /** - * String-based form of qualifiers; When looking for a suitable mapping method to map this iterable mapping method's - * element type, 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 { }; - - /** - * Specifies the type of the element to be used in the result of the mapping method in case multiple mapping - * methods qualify. - * - * @return the elementTargetType to select - */ - Class elementTargetType() default void.class; - - - /** - * The strategy to be applied when {@code null} is passed as source value to this iterable mapping. If no - * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or - * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} - * by default. - * - * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. - */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; -} diff --git a/core-common/src/main/java/org/mapstruct/MapMapping.java b/core-common/src/main/java/org/mapstruct/MapMapping.java deleted file mode 100644 index d2fd3134e1..0000000000 --- a/core-common/src/main/java/org/mapstruct/MapMapping.java +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.text.SimpleDateFormat; -import java.text.DecimalFormat; -import java.util.Date; - -/** - * Configures the mapping between two map types, e.g. {@code Map} and {@code Map}. - * - *

Note: at least one element needs to be specified

- * - * @author Gunnar Morling - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface MapMapping { - - /** - * 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. - * - * @return A date format string as processable by {@link SimpleDateFormat}. - */ - String keyDateFormat() default ""; - - /** - * 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. - * - * @return A date format string as processable by {@link SimpleDateFormat}. - */ - 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. - * - * @return A decimal format string as processable by {@link DecimalFormat}. - */ - 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. - * - * @return A decimal format string as processable by {@link DecimalFormat}. - */ - String valueNumberFormat() 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' - * error. - * - * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. - * - * @return the qualifiers - */ - Class[] keyQualifiedBy() default { }; - - /** - * String-based form of qualifiers; When looking for a suitable mapping method to map this map mapping method's key - * type, 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 #keyQualifiedBy() - * @see Named - */ - String[] keyQualifiedByName() default { }; - - - /** - * A value qualifier can be specified to aid the selection process of a suitable mapper for the values in the map. - * This is useful in case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping - * methods found' error. - *

- * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. - * - * @return the qualifiers - */ - Class[] valueQualifiedBy() default { }; - - /** - * String-based form of qualifiers; When looking for a suitable mapping method to map this map mapping method's - * value type, 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 #valueQualifiedBy() - * @see Named - */ - String[] valueQualifiedByName() default { }; - - /** - * Specifies the type of the key to be used in the result of the mapping method in case multiple mapping - * methods qualify. - * - * - * @return the resultType to select - */ - Class keyTargetType() default void.class; - - /** - * Specifies the type of the value to be used in the result of the mapping method in case multiple mapping - * methods qualify. - * - * - * @return the resultType to select - */ - Class valueTargetType() default void.class; - - - /** - * The strategy to be applied when {@code null} is passed as source value to this map mapping. If no - * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or - * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} - * by default. - * - * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. - */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; -} diff --git a/core-common/src/main/java/org/mapstruct/Mapper.java b/core-common/src/main/java/org/mapstruct/Mapper.java deleted file mode 100644 index 0dc18e43b5..0000000000 --- a/core-common/src/main/java/org/mapstruct/Mapper.java +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.mapstruct.factory.Mappers; - -/** - * Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via - * MapStruct. - * - * @author Gunnar Morling - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.CLASS) -public @interface Mapper { - - /** - * Other mapper types used by this mapper. May be hand-written classes or other mappers generated by MapStruct. No - * cycle between generated mapper classes must be created. - * - * @return The mapper types used by this mapper. - */ - Class[] uses() default { }; - - /** - * Additional types for which an import statement is to be added to the generated mapper implementation class. - * This allows to refer to those types from within mapping expressions given via {@link Mapping#expression()} using - * their simple name rather than their fully-qualified name. - * - * @return classes to add in the imports of the generated implementation. - */ - Class[] imports() default { }; - - /** - * How unmapped properties of the target type of a mapping should be - * reported. The method overrides an unmappedTargetPolicy set in a central - * configuration set by {@link #config() } - * - * @return The reporting policy for unmapped target properties. - */ - ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN; - - /** - * Specifies the component model to which the generated mapper should - * adhere. Supported values are - *

    - *
  • {@code default}: the mapper uses no component model, instances are - * typically retrieved via {@link Mappers#getMapper(Class)}
  • - *
  • - * {@code cdi}: the generated mapper is an application-scoped CDI bean and - * can be retrieved via {@code @Inject}
  • - *
  • - * {@code spring}: the generated mapper is a Spring bean and - * 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}
  • - *
- * The method overrides an unmappedTargetPolicy set in a central configuration set - * by {@link #config() } - * - * @return The component model for the generated mapper. - */ - String componentModel() default "default"; - - /** - * Specifies the name of the implementation class. The {@code } will be replaced by the - * interface/abstract class name. - *

- * Defaults to postfixing the name with {@code Impl}: {@code Impl} - * - * @return The implementation name. - * @see #implementationPackage() - */ - String implementationName() default "Impl"; - - /** - * Specifies the target package for the generated implementation. The {@code } will be replaced by the - * interface's or abstract class' package. - *

- * Defaults to using the same package as the mapper interface/abstract class - * - * @return the implementation package. - * @see #implementationName() - */ - String implementationPackage() default ""; - - /** - * A class annotated with {@link MapperConfig} which should be used as configuration template. Any settings given - * via {@link Mapper} will take precedence over the settings from the referenced configuration source. The list of - * referenced mappers will contain all mappers given via {@link Mapper#uses()} and {@link MapperConfig#uses()}. - * - * @return A class which should be used as configuration template. - */ - Class config() default void.class; - - /** - * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans - * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder - * method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}). - *

- * Any setting given for this attribute will take precedence over {@link MapperConfig#collectionMappingStrategy()}, - * if present. - * - * @return The strategy applied when propagating the value of collection-typed properties. - */ - CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; - - /** - * The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. If no - * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} will be applied, - * using {@link NullValueMappingStrategy#RETURN_NULL} by default. - * - * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. - */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; - - /** - * The strategy to use for applying method-level configuration annotations of prototype methods in the interface - * specified with {@link #config()}. Annotations that can be inherited are for example {@link Mapping}, - * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. - *

- * If no strategy is configured, the strategy given via {@link MapperConfig#mappingInheritanceStrategy()} will be - * applied, using {@link MappingInheritanceStrategy#EXPLICIT} as default. - * - * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface - * specified with {@link #config()}. - */ - MappingInheritanceStrategy mappingInheritanceStrategy() default MappingInheritanceStrategy.EXPLICIT; - - /** - * Determines when to include a null check on the source property value of a bean mapping. - * - * Can be overridden by the one on {@link MapperConfig} or {@link Mapping}. - * - * @return strategy how to do null checking - */ - NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; -} diff --git a/core-common/src/main/java/org/mapstruct/MapperConfig.java b/core-common/src/main/java/org/mapstruct/MapperConfig.java deleted file mode 100644 index 2361173d2a..0000000000 --- a/core-common/src/main/java/org/mapstruct/MapperConfig.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.mapstruct.factory.Mappers; - -/** - * Marks a class or interface as configuration source for generated mappers. This allows to share common configurations - * between several mapper classes. - *

- * Generally, any settings given via {@link Mapper} take precedence over the settings given via the referenced - * {@code MapperConfig}. The lists of referenced mappers given via {@link Mapper#uses()} and - * {@link MapperConfig#uses()} will be merged. - *

- * Additionally, mapper configuration classes may declare one more prototype mapping methods. These methods are - * not meant to be invoked themselves (no implementation will generated for mapper config classes), but serve as - * configuration template for mapping methods declared by actual mapper classes. Depending on the configured - * {@link #mappingInheritanceStrategy()}, the configuration can be inherited either explicitly using - * {@link InheritConfiguration} or {@link InheritInverseConfiguration}, or automatically in case all source and target - * types are assignable. - *

- * - * @author Sjaak Derksen - * @see Mapper#config() - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.CLASS) -public @interface MapperConfig { - - /** - * The mapper types used by this mapper. - * - * @return The mapper types used by this mapper. - */ - Class[] uses() default { }; - - /** - * How unmapped properties of the target type of a mapping should be - * reported. - * - * @return The reporting policy for unmapped target properties. - */ - ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN; - - /** - * Specifies the component model to which the generated mapper should - * adhere. Supported values are - *
    - *
  • {@code default}: the mapper uses no component model, instances are - * typically retrieved via {@link Mappers#getMapper(Class)}
  • - *
  • - * {@code cdi}: the generated mapper is an application-scoped CDI bean and - * can be retrieved via {@code @Inject}
  • - *
  • - * {@code spring}: the generated mapper is a Spring bean and - * 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}
  • - *
- * - * @return The component model for the generated mapper. - */ - String componentModel() default "default"; - - /** - * Specifies the name of the implementation class. The {@code } will be replaced by the - * interface/abstract class name. - *

- * Defaults to postfixing the name with {@code Impl}: {@code Impl} - * - * @return The implementation name. - * @see #implementationPackage() - */ - String implementationName() default "Impl"; - - /** - * Specifies the target package for the generated implementation. The {@code } will be replaced by the - * interface's or abstract class' package. - *

- * Defaults to using the same package as the mapper interface/abstract class - * - * @return the implementation package. - * @see #implementationName() - */ - String implementationPackage() default ""; - - /** - * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans - * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder - * method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}). - * - * @return The strategy applied when propagating the value of collection-typed properties. - */ - CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; - - /** - * The strategy to be applied when {@code null} is passed as source value to mapping methods. If no strategy is - * configured, {@link NullValueMappingStrategy#RETURN_NULL} will be used by default. - * - * @return The strategy to be applied when {@code null} is passed as source value to mapping methods. - */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; - - /** - * The strategy to use for applying method-level configuration annotations of prototype methods in the interface - * annotated with this annotation. Annotations that can be inherited are for example {@link Mapping}, - * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. - *

- * If no strategy is configured, {@link MappingInheritanceStrategy#EXPLICIT} will be used as default. - * - * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface - * annotated with this annotation. - */ - MappingInheritanceStrategy mappingInheritanceStrategy() - default MappingInheritanceStrategy.EXPLICIT; - - /** - * Determines when to include a null check on the source property value of a bean mapping. - * - * Can be overridden by the one on {@link Mapper} or {@link Mapping}. - * - * @return strategy how to do null checking - */ - NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; -} diff --git a/core-common/src/main/java/org/mapstruct/MappingConstants.java b/core-common/src/main/java/org/mapstruct/MappingConstants.java deleted file mode 100644 index dd9ff487e1..0000000000 --- a/core-common/src/main/java/org/mapstruct/MappingConstants.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -/** - * Contains all constants defined in the mapping process. - * - * @author Sjaak Derksen - */ -public final class MappingConstants { - - private MappingConstants() { - } - - /** - * Represents a {@code null} source or target. - */ - public static final String NULL = ""; - - /** - * Represents any source that is not already mapped by either a defined mapping or by means of name based mapping. - */ - public static final String ANY_REMAINING = ""; - - /** - * Represents any source that is not already mapped by a defined mapping. - */ - public static final String ANY_UNMAPPED = ""; - -} diff --git a/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java b/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java deleted file mode 100644 index 5e2445778c..0000000000 --- a/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -/** - * Strategy for inheriting configurations given for methods of prototype mapping methods (declared on mapper config - * classes) to actual mapping methods declared on mappers referring to such config class via {@link Mapper#config()}. - * - * @author Andreas Gudian - */ -public enum MappingInheritanceStrategy { - /** - * Apply the method-level configuration annotations only if the prototype method is explicitly referenced using - * {@link InheritConfiguration}. - */ - EXPLICIT, - - /** - * Inherit the method-level configuration annotations automatically if source and target types of the prototype - * method are assignable from the types of a given mapping method. - */ - AUTO_INHERIT_FROM_CONFIG; -} diff --git a/core-common/src/main/java/org/mapstruct/MappingTarget.java b/core-common/src/main/java/org/mapstruct/MappingTarget.java deleted file mode 100644 index fa500b1df7..0000000000 --- a/core-common/src/main/java/org/mapstruct/MappingTarget.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Declares a parameter of a mapping method to be the target of the mapping. - *

- * Not more than one parameter can be declared as {@code MappingTarget}. - * - * @author Andreas Gudian - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.CLASS) -public @interface MappingTarget { -} diff --git a/core-common/src/main/java/org/mapstruct/Named.java b/core-common/src/main/java/org/mapstruct/Named.java deleted file mode 100644 index 626735b4cf..0000000000 --- a/core-common/src/main/java/org/mapstruct/Named.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marks mapping methods with the given qualifier name. Can be used to qualify a single method or all methods of a given - * type by specifying this annotation on the type level. - *

- * Will be used to to select the correct mapping methods when mapping a bean property type, element of an iterable type - * or the key/value of a map type. - *

- * Example (both methods of {@code Titles} are capable to convert a string, but the ambiguity is resolved by applying - * the qualifiers in {@code @Mapping}: - * - *

- * 
- * @Named("TitleTranslator")
- * public class Titles {
- *
- *   @Named("EnglishToGerman")
- *   public String translateTitleEG(String title) {
- *       // some mapping logic
- *   }
- *
- *   @Named("GermanToEnglish")
- *   public String translateTitleGE(String title) {
- *       // some mapping logic
- *   }
- * }
- *
- * @Mapper( uses = Titles.class )
- * public interface MovieMapper {
- *
- *    @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
- *    GermanRelease toGerman( OriginalRelease movies );
- *
- * }
- * 
- * 
- * - * The following implementation of {@code MovieMapper} will be generated: - * - *
- * 
- *
- * public class MovieMapperImpl implements MovieMapper {
- *     private final Titles titles = new Titles();
- *
- *     @Override
- *     public GermanRelease toGerman(OriginalRelease movies) {
- *         if ( movies == null ) {
- *             return null;
- *         }
- *
- *         GermanRelease germanRelease = new GermanRelease();
- *
- *         germanRelease.setTitle( titles.translateTitleEG( movies.getTitle() ) );
- *
- *         return germanRelease;
- *     }
- * }
- * 
- * 
- * - * @author Sjaak Derksen - * @see org.mapstruct.Mapping#qualifiedByName() - * @see IterableMapping#qualifiedByName() - * @see MapMapping#keyQualifiedByName() - * @see MapMapping#valueQualifiedByName() - */ -@Target( { ElementType.TYPE, ElementType.METHOD } ) -@Retention( RetentionPolicy.CLASS ) -@Qualifier -public @interface Named { - - /** - * A name qualifying the annotated element - * - * @return the name. - */ - String value(); -} diff --git a/core-common/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core-common/src/main/java/org/mapstruct/NullValueCheckStrategy.java deleted file mode 100644 index d7f6b7bf18..0000000000 --- a/core-common/src/main/java/org/mapstruct/NullValueCheckStrategy.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -/** - * Strategy for dealing with null source values. - * - * Note: This strategy is not in effect when the a specific source presence check method is defined - * in the service provider interface (SPI). - * - * @author Sean Huang - */ -public enum NullValueCheckStrategy { - - /** - * This option includes a null check. When: - *

- *

    - *
  1. a source value is directly assigned to a target
  2. - *
  3. a source value assigned to a target by calling a type conversion on the target first
  4. - *
- *

- * NOTE: mapping methods (generated or hand written) are excluded from this null check. They are intended to - * handle a null source value as 'valid' input. - * - */ - ON_IMPLICIT_CONVERSION, - - /** - * This option always includes a null check. - */ - ALWAYS, - -} diff --git a/core-common/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core-common/src/main/java/org/mapstruct/NullValueMappingStrategy.java deleted file mode 100644 index aea2f40ed6..0000000000 --- a/core-common/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -/** - * Strategy for dealing with {@code null} values passed to mapping methods. - * - * @author Sjaak Derksen - */ -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. - */ - RETURN_NULL, - - /** - * If {@code null} is passed to a mapping method, a default value will be returned. The value depends on the kind of - * the annotated method: - *

    - *
  • For bean mapping methods the target type will be instantiated and returned. Any properties of the target type - * which are mapped via {@link Mapping#expression()} or {@link Mapping#constant()} will be populated based on the - * given expression or constant. Note that expressions must be prepared to deal with {@code null} values in this - * case.
  • - *
  • For iterable mapping methods an empty collection will be returned.
  • - *
  • For map mapping methods an empty map will be returned.
  • - *
- */ - RETURN_DEFAULT; -} diff --git a/core-common/src/main/java/org/mapstruct/ObjectFactory.java b/core-common/src/main/java/org/mapstruct/ObjectFactory.java deleted file mode 100644 index 169e8f4a19..0000000000 --- a/core-common/src/main/java/org/mapstruct/ObjectFactory.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * By default beans are created during the mapping process with the default constructor. - * This method allows to mark a method as a factory method to create beans. - * The type of the factory method is determined by its return type. A factory is used - * to create a bean if their types match. - *

- * The factory method can retrieve the mapping sources by specifying - * them as parameters, exactly like mapping and lifecycle methods do. This allows the factory - * method to create target beans based on source beans. - * The use of source parameters opens up a variety of possibilities. For example, - * a factory method for entities can look up an - * EntityManager to check whether the entity already exists and then return its - * managed instance. - * - *

- * {@literal @}ApplicationScoped // CDI component model
- * public class ReferenceMapper {
- *
- *     {@literal @}PersistenceContext
- *     private EntityManager em;
- *
- *     {@literal @}ObjectFactory
- *     public SomeEntity resolve(SomeDto dto) {
- *         SomeEntity entity = em.find(SomeEntity.class, dto.getId());
- *         return entity != null ? entity : new SomeEntity();
- *     }
- * }
- * 
- * - * If no such parameters - * are provided, the use of this annotation is optional. - * - *

- * If there are two factory methods, both serving the same type, one with no parameters - * and one taking sources as input, then the one with the source parameters is favored. - * If multiple factory method take sources as input, them the one is chosen which allows a - * valid assignment from mapping source parameters to those factory parameters. If - * there are multiple such factories, an ambiguity exception is thrown. - *

- * - * @author Remo Meier - * @since 1.2 - */ -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) -public @interface ObjectFactory { - -} diff --git a/core-common/src/main/java/org/mapstruct/Qualifier.java b/core-common/src/main/java/org/mapstruct/Qualifier.java deleted file mode 100644 index 9a5406b9e4..0000000000 --- a/core-common/src/main/java/org/mapstruct/Qualifier.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Declares an annotation type to be a qualifier. Qualifier annotations allow unambiguously identify a suitable mapping - * method in case several methods qualify to map a bean property, iterable element etc. - *

- * For more info see: - *

    - *
  • {@link Mapping#qualifiedBy() }
  • - *
  • {@link IterableMapping#qualifiedBy() }
  • - *
  • {@link MapMapping#keyQualifiedBy() }
  • - *
  • {@link MapMapping#valueQualifiedBy() }
  • - *
- * Example: - * - *
- * @Qualifier
- * @Target(ElementType.METHOD)
- * @Retention(RetentionPolicy.CLASS)
- * public @interface EnglishToGerman {
- * }
- * 
- * - * NOTE: Qualifiers should have {@link RetentionPolicy#CLASS}. - * - * @author Sjaak Derksen - */ -@Target(ElementType.ANNOTATION_TYPE) -@Retention(RetentionPolicy.CLASS) -public @interface Qualifier { -} diff --git a/core-common/src/main/java/org/mapstruct/ReportingPolicy.java b/core-common/src/main/java/org/mapstruct/ReportingPolicy.java deleted file mode 100644 index 21c1e8b55a..0000000000 --- a/core-common/src/main/java/org/mapstruct/ReportingPolicy.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import javax.tools.Diagnostic.Kind; - -/** - * Policy for reporting issues occurring during the generation of a mapper - * implementation. - * - * @author Gunnar Morling - */ -public enum ReportingPolicy { - - /** - * No report will be created for the given issue. - */ - IGNORE, - - /** - * A report with {@link Kind#WARNING} will be created for the given issue. - */ - WARN, - - /** - * A report with {@link Kind#ERROR} will be created for the given issue, - * causing the compilation to fail. - */ - ERROR; -} diff --git a/core-common/src/main/java/org/mapstruct/TargetType.java b/core-common/src/main/java/org/mapstruct/TargetType.java deleted file mode 100644 index 806c72048e..0000000000 --- a/core-common/src/main/java/org/mapstruct/TargetType.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Declares a parameter of a custom mapping method to be populated with the target type of the mapping. - *

- * Not more than one parameter can be declared as {@code TargetType} and that parameter needs to be of type - * {@link Class} (may be parameterized), or a super-type of it. - * - * @author Andreas Gudian - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.CLASS) -public @interface TargetType { -} diff --git a/core-common/src/main/java/org/mapstruct/factory/Mappers.java b/core-common/src/main/java/org/mapstruct/factory/Mappers.java deleted file mode 100644 index 1d188ba8ae..0000000000 --- a/core-common/src/main/java/org/mapstruct/factory/Mappers.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.factory; - -import java.util.ArrayList; -import java.util.List; -import java.util.ServiceLoader; - -import org.mapstruct.Mapper; - -/** - * Factory for obtaining mapper instances if no explicit component model such as CDI is configured via - * {@link Mapper#componentModel()}. - *

- * Mapper implementation types are expected to have the same fully qualified name as their interface type, with the - * suffix {@code Impl} appended. When using this factory, mapper types - and any mappers they use - are instantiated by - * invoking their public no-args constructor. - *

- * By convention, a single instance of each mapper is retrieved from the factory and exposed on the mapper interface - * type by declaring a member named {@code INSTANCE} like this: - * - *

- * @Mapper
- * public interface CustomerMapper {
- *
- *     CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
- *
- *     // mapping methods...
- * }
- * 
- * - * @author Gunnar Morling - */ -public class Mappers { - - private static final String IMPLEMENTATION_SUFFIX = "Impl"; - - private Mappers() { - } - - /** - * Returns an instance of the given mapper type. - * - * @param clazz The type of the mapper to return. - * @param The type of the mapper to create. - * - * @return An instance of the given mapper type. - */ - public static T getMapper(Class clazz) { - try { - List classLoaders = new ArrayList( 3 ); - classLoaders.add( clazz.getClassLoader() ); - - if ( Thread.currentThread().getContextClassLoader() != null ) { - classLoaders.add( Thread.currentThread().getContextClassLoader() ); - } - - classLoaders.add( Mappers.class.getClassLoader() ); - - return getMapper( clazz, classLoaders ); - } - catch ( ClassNotFoundException e ) { - throw new RuntimeException( e ); - } - } - - private static T getMapper( - Class mapperType, Iterable classLoaders) throws ClassNotFoundException { - - for ( ClassLoader classLoader : classLoaders ) { - T mapper = doGetMapper( mapperType, classLoader ); - if ( mapper != null ) { - return mapper; - } - } - - throw new ClassNotFoundException("Cannot find implementation for " + mapperType.getName() ); - } - - private static T doGetMapper(Class clazz, ClassLoader classLoader) { - try { - @SuppressWarnings("unchecked") - T mapper = (T) classLoader.loadClass( clazz.getName() + IMPLEMENTATION_SUFFIX ).newInstance(); - return mapper; - } - catch (ClassNotFoundException e) { - ServiceLoader loader = ServiceLoader.load( clazz, classLoader ); - - if ( loader != null ) { - for ( T mapper : loader ) { - if ( mapper != null ) { - return mapper; - } - } - } - - return null; - } - catch (InstantiationException e) { - throw new RuntimeException( e ); - } - catch (IllegalAccessException e) { - throw new RuntimeException( e ); - } - } -} diff --git a/core-common/src/main/java/org/mapstruct/factory/package-info.java b/core-common/src/main/java/org/mapstruct/factory/package-info.java deleted file mode 100644 index 5b07013633..0000000000 --- a/core-common/src/main/java/org/mapstruct/factory/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - *

- * Provides the {@link org.mapstruct.factory.Mappers} factory class for the retrieval of mapper instances if no - * explicit component model such as CDI is configured via {@link org.mapstruct.Mapper#componentModel()}. - *

- * - */ -package org.mapstruct.factory; diff --git a/core-common/src/main/java/org/mapstruct/package-info.java b/core-common/src/main/java/org/mapstruct/package-info.java deleted file mode 100644 index 123d4c68f2..0000000000 --- a/core-common/src/main/java/org/mapstruct/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - *

- * MapStruct is a code generator which simplifies the implementation of mappings between Java bean types by generating - * mapping code at compile time, following a convention-over-configuration approach. The generated code uses plain - * method invocations and thus is fast and type-safe. - *

- *

- * This package contains several annotations which allow to configure how mapper interfaces are generated. - *

- * - * @see MapStruct reference documentation - */ -package org.mapstruct; diff --git a/core-common/src/main/java/org/mapstruct/util/Experimental.java b/core-common/src/main/java/org/mapstruct/util/Experimental.java deleted file mode 100644 index 00514396f7..0000000000 --- a/core-common/src/main/java/org/mapstruct/util/Experimental.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.util; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Features that are marked with this annotation are considered experimental. - * - * @author Andreas Gudian - */ -@Documented -@Retention(RetentionPolicy.SOURCE) -public @interface Experimental { - -} diff --git a/core-common/src/test/java/org/mapstruct/factory/MappersTest.java b/core-common/src/test/java/org/mapstruct/factory/MappersTest.java deleted file mode 100644 index a9758c9ceb..0000000000 --- a/core-common/src/test/java/org/mapstruct/factory/MappersTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.factory; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.mapstruct.test.model.Foo; - -/** - * Unit test for {@link Mappers}. - * - * @author Gunnar Morling - */ -public class MappersTest { - - @Test - public void shouldReturnImplementationInstance() { - - Foo mapper = Mappers.getMapper( Foo.class ); - assertThat( mapper ).isNotNull(); - } -} diff --git a/core-common/src/test/java/org/mapstruct/test/model/Foo.java b/core-common/src/test/java/org/mapstruct/test/model/Foo.java deleted file mode 100644 index 79555b8c68..0000000000 --- a/core-common/src/test/java/org/mapstruct/test/model/Foo.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.test.model; - -public interface Foo { - -} diff --git a/core-common/src/test/java/org/mapstruct/test/model/FooImpl.java b/core-common/src/test/java/org/mapstruct/test/model/FooImpl.java deleted file mode 100644 index 09e6797cef..0000000000 --- a/core-common/src/test/java/org/mapstruct/test/model/FooImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.test.model; - -public class FooImpl implements Foo { - -} diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 0cb8784572..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -1,22 +1,9 @@ @@ -25,142 +12,18 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml mapstruct-jdk8 - jar MapStruct Core JDK 8 - MapStruct annotations to be used with JDK 8 and later - - - copied-common-sources - - - - - ${project.groupId} - mapstruct-common - true - - - junit - junit - test - - - org.assertj - assertj-core - test - - + Deprecated MapStruct artifact containing annotations to be used with JDK 8 and later - + Relocated to mapstruct + + + mapstruct + + - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - verify - - jar - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-copied-common-sources - generate-sources - - add-source - - - - ${project.build.directory}/${common.sources.dir} - - - - - - - org.apache.maven.plugins - maven-resources-plugin - - - copy-resources - process-resources - - copy-resources - - - ${project.build.directory}/${common.sources.dir} - - - ${basedir}/../core-common/src/main/java - false - - - - - - copy-mapstruct-license - prepare-package - - copy-resources - - - ${project.build.directory}/classes/META-INF - - - ${basedir}/.. - false - LICENSE.txt - - - - - - - - org.apache.felix - maven-bundle-plugin - - - maven-jar-plugin - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - - - - diff --git a/core-jdk8/src/main/java/org/mapstruct/Mapping.java b/core-jdk8/src/main/java/org/mapstruct/Mapping.java deleted file mode 100644 index ff217389c7..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/Mapping.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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 java.text.SimpleDateFormat; -import java.text.DecimalFormat; -import java.util.Date; - -/** - * Configures the mapping of one bean attribute or enum constant. - *

- * 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 - * {@link #source()}, {@link #expression()} or {@link #constant()} can be specified to define the property source. - *

- * In addition, the attributes {@link #dateFormat()} and {@link #qualifiedBy()} may be used to further define the - * mapping. - * - *

- * IMPORTANT NOTE: the enum mapping capability is deprecated and replaced by {@link ValueMapping} it - * will be removed in subsequent versions. - * - * @author Gunnar Morling - */ -@Repeatable(Mappings.class) -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) -public @interface Mapping { - - /** - * The target name of the configured property as defined by the JavaBeans specification. The same target property - * must not be mapped more than once. - *

- * If used to map an enum constant, the name of the constant member is to be given. In this case, several values - * from the source enum may be mapped to the same value of the target enum. - * - * @return The target name of the configured property or enum constant - */ - String target(); - - /** - * The source to use for this mapping. This can either be: - *

    - *
  1. The source name of the configured property as defined by the JavaBeans specification. - *

    - * This may either be a simple property name (e.g. "address") or a dot-separated property path (e.g. "address.city" - * or "address.city.name"). In case the annotated method has several source parameters, the property name must - * qualified with the parameter name, e.g. "addressParam.city".

  2. - *
  3. When no matching property is found, MapStruct looks for a matching parameter name instead.
  4. - *
  5. When used to map an enum constant, the name of the constant member is to be given.
  6. - *
- * This attribute can not be used together with {@link #constant()} or {@link #expression()}. - * - * @return The source name of the configured property or enum constant. - */ - String source() default ""; - - /** - * 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. - * - * @return A date format string as processable by {@link SimpleDateFormat}. - */ - 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. - * - * @return A decimal format string as processable by {@link DecimalFormat}. - */ - String numberFormat() default ""; - - /** - * A constant {@link String} based on which the specified target property is to be set. If the designated target - * property is not of type {@code String}, the value will be converted by applying a matching conversion method or - * built-in conversion. - *

- * This attribute can not be used together with {@link #source()}, {@link #defaultValue()} or {@link #expression()}. - * - * @return A constant {@code String} constant specifying the value for the designated target property - */ - String constant() default ""; - - /** - * An expression {@link String} based on which the specified target property is to be set. - *

- * Currently, Java is the only supported "expression language" and expressions must be given in form of Java - * expressions using the following format: {@code java()}. For instance the mapping: - *


-     * @Mapping(
-     *     target = "someProp",
-     *     expression = "java(new TimeAndFormat( s.getTime(), s.getFormat() ))"
-     * )
-     * 
- *

- * will cause the following target property assignment to be generated: - *

- * {@code targetBean.setSomeProp( new TimeAndFormat( s.getTime(), s.getFormat() ) )}. - *

- * Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be - * imported via {@link Mapper#imports()}. - *

- * This attribute can not be used together with {@link #source()}, {@link #defaultValue()} or {@link #constant()}. - * - * @return An expression specifying the value for the designated target property - */ - String expression() default ""; - - /** - * 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 - * the target object are populated using a decorator and thus would be reported as unmapped target property by - * default. - * - * @return {@code true} if the given property should be ignored, {@code false} otherwise - */ - boolean ignore() default false; - - /** - * 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 - */ - Class[] 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 { }; - - /** - * Specifies the result type of the mapping method to be used in case multiple mapping methods qualify. - * - * @return the resultType to select - */ - Class resultType() default void.class; - - /** - * One or more properties of the result type on which the mapped property depends. The generated method - * implementation will invoke the setters of the result type ordered so that the given dependency relationship(s) - * are satisfied. Useful in case one property setter depends on the state of another property of the result type. - *

- * An error will be raised in case a cycle in the dependency relationships is detected. - * - * @return the dependencies of the mapped property - */ - String[] dependsOn() default { }; - - /** - * In case the source property is {@code null}, the provided default {@link String} value is set. If the designated - * target property is not of type {@code String}, the value will be converted by applying a matching conversion - * method or built-in conversion. - *

- * This attribute can not be used together with {@link #constant()} or {@link #expression()}. - * - * @return Default value to set in case the source property is {@code null}. - */ - String defaultValue() default ""; -} diff --git a/core-jdk8/src/main/java/org/mapstruct/Mappings.java b/core-jdk8/src/main/java/org/mapstruct/Mappings.java deleted file mode 100644 index 4d5e514304..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/Mappings.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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 mappings of several bean attributes. - * - * @author Gunnar Morling - */ -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) -public @interface Mappings { - - /** - * The configuration of the bean attributes. - * - * @return The configuration of the bean attributes. - */ - Mapping[] value(); -} diff --git a/core-jdk8/src/main/java/org/mapstruct/ValueMapping.java b/core-jdk8/src/main/java/org/mapstruct/ValueMapping.java deleted file mode 100644 index 36ad91b14a..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/ValueMapping.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -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 mapping of source constant value to target constant value. - *

- * Supported mappings are - *

    - *
  1. Enumeration to Enumeration
  2. - *
- *

- * Example 1: - * - *

- * 
- * public enum OrderType { RETAIL, B2B, EXTRA, STANDARD, NORMAL }
- *
- * public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT }
- *
- * @ValueMapping(source = "EXTRA", target = "SPECIAL"),
- * @ValueMapping(source = "STANDARD", target = "DEFAULT"),
- * @ValueMapping(source = "NORMAL", target = "DEFAULT")
- * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
- * 
- * Mapping result:
- * +---------------------+----------------------------+
- * | OrderType           | ExternalOrderType          |
- * +---------------------+----------------------------+
- * | null                | null                       |
- * | OrderType.EXTRA     | ExternalOrderType.SPECIAL  |
- * | OrderType.STANDARD  | ExternalOrderType.DEFAULT  |
- * | OrderType.NORMAL    | ExternalOrderType.DEFAULT  |
- * | OrderType.RETAIL    | ExternalOrderType.RETAIL   |
- * | OrderType.B2B       | ExternalOrderType.B2B      |
- * +---------------------+----------------------------+
- * 
- * - * MapStruct will WARN on incomplete mappings. However, if for some reason no match is found an - * {@link java.lang.IllegalStateException} will be thrown. - *

- * Example 2: - * - *

- * 
- * @ValueMapping( source = "<NULL>", target = "DEFAULT" ),
- * @ValueMapping( source = "STANDARD", target = "<NULL>" ),
- * @ValueMapping( source = "<ANY_REMAINING>", target = "SPECIAL" )
- * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
- * 
- * Mapping result:
- * +---------------------+----------------------------+
- * | OrderType           | ExternalOrderType          |
- * +---------------------+----------------------------+
- * | null                | ExternalOrderType.DEFAULT  |
- * | OrderType.STANDARD  | null                       |
- * | OrderType.RETAIL    | ExternalOrderType.RETAIL   |
- * | OrderType.B2B       | ExternalOrderType.B2B      |
- * | OrderType.NORMAL    | ExternalOrderType.SPECIAL  |
- * | OrderType.EXTRA     | ExternalOrderType.SPECIAL  |
- * +---------------------+----------------------------+
- * 
- * - * @author Sjaak Derksen - */ -@Repeatable(ValueMappings.class) -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) -public @interface ValueMapping { - /** - * The source value constant to use for this mapping. - * - *

- * Valid values: - *

    - *
  1. enum constant name
  2. - *
  3. {@link MappingConstants#NULL}
  4. - *
  5. {@link MappingConstants#ANY_REMAINING}
  6. - *
  7. {@link MappingConstants#ANY_UNMAPPED}
  8. - *
- *

- * NOTE:When using <ANY_REMAINING>, MapStruct will perform the normal name based mapping, in which - * source is mapped to target based on enum identifier equality. Using <ANY_UNMAPPED> will not apply name - * based mapping. - * - * @return The source value. - */ - String source(); - - /** - * The target value constant to use for this mapping. - * - *

- * Valid values: - *

    - *
  1. enum constant name
  2. - *
  3. {@link MappingConstants#NULL}
  4. - *
- * - * @return The target value. - */ - String target(); - -} diff --git a/core-jdk8/src/main/java/org/mapstruct/ValueMappings.java b/core-jdk8/src/main/java/org/mapstruct/ValueMappings.java deleted file mode 100644 index 24fcbf55de..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/ValueMappings.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Constructs a set of value (constant) mappings. - * - * @author Sjaak Derksen - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface ValueMappings { - - ValueMapping[] value(); - -} diff --git a/core/pom.xml b/core/pom.xml index ed7dc5232c..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -33,19 +20,10 @@ jar MapStruct Core - - copied-common-sources - - - ${project.groupId} - mapstruct-common - true - - - junit - junit + org.junit.jupiter + junit-jupiter test @@ -83,44 +61,10 @@ - - org.codehaus.mojo - build-helper-maven-plugin - - - add-copied-common-sources - generate-sources - - add-source - - - - ${project.build.directory}/${common.sources.dir} - - - - - org.apache.maven.plugins maven-resources-plugin - - copy-resources - process-resources - - copy-resources - - - ${project.build.directory}/${common.sources.dir} - - - ${basedir}/../core-common/src/main/java - false - - - - copy-mapstruct-license prepare-package @@ -143,6 +87,11 @@ org.apache.felix maven-bundle-plugin + + + org.mapstruct + + maven-jar-plugin @@ -152,6 +101,10 @@ + + com.github.siom79.japicmp + japicmp-maven-plugin + diff --git a/core/src/main/java/org/mapstruct/AfterMapping.java b/core/src/main/java/org/mapstruct/AfterMapping.java new file mode 100644 index 0000000000..a76eebdcba --- /dev/null +++ b/core/src/main/java/org/mapstruct/AfterMapping.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 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method to be invoked at the end of a generated mapping method, right before the last {@code return} statement + * of the mapping method. The method can be implemented in an abstract mapper class, be declared in a type (class or + * interface) referenced in {@link Mapper#uses()}, or in a type used as {@code @}{@link Context} parameter in order to + * be used in a mapping method. + *

+ * The method invocation is only generated if the return type of the method (if non-{@code void}) is assignable to the + * return type of the mapping method and all parameters can be assigned by the available source, target or + * context parameters of the mapping method: + *

    + *
  • A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping. + *
  • + *
  • A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.
  • + *
  • Parameters annotated with {@code @}{@link Context} are populated with the context parameters of the mapping + * method.
  • + *
  • Any other parameter is populated with a source parameter of the mapping.
  • + *
+ *

+ * For non-{@code void} methods, the return value of the method invocation is returned as the result of the mapping + * method if it is not {@code null}. + *

+ * All after-mapping methods that can be applied to a mapping method will be used. {@code @}{@link Qualifier} / + * {@code @}{@link Named} can be used to filter the methods to use. + *

+ * The order of the method invocation is determined by their location of definition: + *

    + *
  1. Methods declared on {@code @}{@link Context} parameters, ordered by the parameter order.
  2. + *
  3. Methods implemented in the mapper itself.
  4. + *
  5. Methods from types referenced in {@link Mapper#uses()}, in the order of the type declaration in the annotation. + *
  6. + *
  7. Methods declared in one type are used after methods declared in their super-type
  8. + *
+ * Important: the order of methods declared within one type can not be guaranteed, as it depends on the + * compiler and the processing environment implementation. + *

+ * Example: + * + *

+ * 
+ * @AfterMapping
+ * public void calledWithoutArgs() {
+ *     // ...
+ * }
+ *
+ * @AfterMapping
+ * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
+ *     // ...
+ * }
+ *
+ * @AfterMapping
+ * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
+ *     // ...
+ * }
+ *
+ * public abstract TargetDto toTargetDto(SourceEntity source);
+ *
+ * // generates:
+ *
+ * public TargetDto toTargetDto(SourceEntity source) {
+ *     if ( source == null ) {
+ *         return null;
+ *     }
+ *
+ *     TargetDto targetDto = new TargetDto();
+ *
+ *     // actual mapping code
+ *
+ *     calledWithoutArgs();
+ *     calledWithSourceAndTargetType( source, TargetDto.class );
+ *     calledWithSourceAndTarget( source, targetDto );
+ *
+ *     return targetDto;
+ * }
+ * 
+ * 
+ * + * @author Andreas Gudian + * @see BeforeMapping + * @see Context + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface AfterMapping { + +} 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. + *

+ * Marking a class as `Lazy`: + * + *

+ * @AnnotateWith( value = Lazy.class )
+ * @Mapper
+ * public interface FooMapper {
+ *     // mapper code
+ * }
+ * 
+ * + * The following code would be generated: + * + *

+ * @Lazy
+ * public class FooMapperImpl implements FooMapper {
+ *     // mapper code
+ * }
+ * 
+ * Setting the profile on the generated implementation: + * + *

+ * @AnnotateWith( value = Profile.class, elements = @AnnotateWith.Element( strings = "prod" ) )
+ * @Mapper
+ * public interface FooMapper {
+ *     // mapper code
+ * }
+ * 
+ * + * 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 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> 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 new file mode 100644 index 0000000000..309458f861 --- /dev/null +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -0,0 +1,227 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +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

+ *

+ * // When result types have an inheritance relation, selecting either mapping method {@link Mapping} or factory method
+ * // {@link BeanMapping} can be become ambiguous. Parameter  {@link BeanMapping#resultType()} can be used.
+ * public class FruitFactory {
+ *     public Apple createApple() {
+ *         return new Apple();
+ *     }
+ *     public Orange createOrange() {
+ *         return new Orange();
+ *     }
+ * }
+ * @Mapper(uses = FruitFactory.class)
+ * public interface FruitMapper {
+ *     @BeanMapping(resultType = Apple.class)
+ *     Fruit toFruit(FruitDto fruitDto);
+ * }
+ * 
+ *

+ * // generates
+ * public class FruitMapperImpl implements FruitMapper {
+ *      @Override
+ *      public Fruit toFruit(FruitDto fruitDto) {
+ *          Apple fruit = fruitFactory.createApple();
+ *          // ...
+ *      }
+ * }
+ * 
+ * + * @author Sjaak Derksen + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface BeanMapping { + + /** + * 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 + */ + Class resultType() default void.class; + + /** + * A qualifier can be specified to aid the selection process of a suitable factory method or filtering applicable + * {@code @}{@link BeforeMapping} / {@code @}{@link AfterMapping} methods. This is useful in case multiple factory + * method (hand written of internal) qualify and result in an 'Ambiguous factory methods' error. + *

+ * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. + * + * @return the qualifiers + * @see Qualifier + */ + Class[] qualifiedBy() default {}; + + /** + * Similar to {@link #qualifiedBy()}, but used in combination with {@code @}{@link Named} in case no custom + * qualifier annotation is defined. + * + * @return the qualifiers + * @see Named + */ + String[] qualifiedByName() default { }; + + /** + * The strategy to be applied when {@code null} is passed as source bean argument value to this bean mapping. If no + * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or + * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} + * by default. + * + * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. + */ + NullValueMappingStrategy nullValueMappingStrategy() 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()} or + * {@link Mapper#nullValuePropertyMappingStrategy()} will be applied, + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @since 1.3 + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() + default NullValuePropertyMappingStrategy.SET_TO_NULL; + + /** + * Determines when to include a null check on the source property value of a bean mapping. + * + * Can be overridden by the one on {@link MapperConfig}, {@link Mapper} or {@link Mapping}. + * + * @return strategy how to do null checking + */ + 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 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 source or target properties. + * + * @return The ignore strategy (default false). + * + * @since 1.3 + */ + boolean ignoreByDefault() default false; + + /** + * Unmapped source properties to be ignored. This could be used when {@link ReportingPolicy#WARN} + * or {@link ReportingPolicy#ERROR} is used for {@link Mapper#unmappedSourcePolicy()} or + * {@link MapperConfig#unmappedSourcePolicy()}. Listed properties will be ignored when composing the unmapped + * 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 + * + * @since 1.3 + */ + 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 + * {@link Mapper#unmappedTargetPolicy()} will be applied, using {@link ReportingPolicy#WARN} by default. + * + * @return The reporting policy for unmapped target properties. + * + * @since 1.5 + */ + ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN; + + /** + * The information that should be used for the builder mappings. This can be used to define custom build methods + * for the builder strategy that one uses. + * + * If no builder is defined the builder given via {@link MapperConfig#builder()} or {@link Mapper#builder()} + * will be applied. + *

+ * NOTE: In case no builder is defined here, in {@link Mapper} or {@link MapperConfig} and there is a single + * build method, then that method would be used. + *

+ * If the builder is defined and there is a single method that does not match the name of the finisher than + * a compile error will occurs + * + * @return the builder information for the method level + * + * @since 1.3 + */ + Builder builder() default @Builder; + + /** + * Allows detailed control over the mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class mappingControl() default MappingControl.class; + +} diff --git a/core/src/main/java/org/mapstruct/BeforeMapping.java b/core/src/main/java/org/mapstruct/BeforeMapping.java new file mode 100644 index 0000000000..0e296e71a7 --- /dev/null +++ b/core/src/main/java/org/mapstruct/BeforeMapping.java @@ -0,0 +1,100 @@ +/* + * 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; + +/** + * Marks a method to be invoked at the beginning of a generated mapping method. The method can be implemented in an + * abstract mapper class, be declared in a type (class or interface) referenced in {@link Mapper#uses()}, or in a type + * used as {@code @}{@link Context} parameter in order to be used in a mapping method. + *

+ * The method invocation is only generated if the return type of the method (if non-{@code void}) is assignable to the + * return type of the mapping method and all parameters can be assigned by the available source, target or + * context parameters of the mapping method: + *

    + *
  • A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping. + *
  • + *
  • A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.
  • + *
  • Parameters annotated with {@code @}{@link Context} are populated with the context parameters of the mapping + * method.
  • + *
  • Any other parameter is populated with a source parameter of the mapping.
  • + *
+ *

+ * For non-{@code void} methods, the return value of the method invocation is returned as the result of the mapping + * method if it is not {@code null}. + *

+ * All before-mapping methods that can be applied to a mapping method will be used. {@code @}{@link Qualifier} + * / {@code @}{@link Named} can be used to filter the methods to use. + *

+ * The order of the method invocation is determined by their their variant and their location of definition: + *

    + *
  1. Methods without an {@code @}{@link MappingTarget} parameter are called before any null-checks on source + * parameters and constructing a new target bean.
  2. + *
  3. Methods with an {@code @}{@link MappingTarget} parameter are called after constructing a new target bean.
  4. + *
  5. Methods declared on {@code @}{@link Context} parameters, ordered by the parameter order.
  6. + *
  7. Methods implemented in the mapper itself.
  8. + *
  9. Methods from types referenced in {@link Mapper#uses()}, in the order of the type declaration in the annotation. + *
  10. + *
  11. Methods declared in one type are used after methods declared in their super-type
  12. + *
+ * Important: the order of methods declared within one type can not be guaranteed, as it depends on the + * compiler and the processing environment implementation. + *

+ * Example: + * + *

+ * 
+ * @BeforeMapping
+ * public void calledWithoutArgs() {
+ *     // ...
+ * }
+ *
+ * @BeforeMapping
+ * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
+ *     // ...
+ * }
+ *
+ * @BeforeMapping
+ * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
+ *     // ...
+ * }
+ *
+ * public abstract TargetDto toTargetDto(SourceEntity source);
+ *
+ * // generates:
+ *
+ * public TargetDto toTargetDto(SourceEntity source) {
+ *     calledWithoutArgs();
+ *     calledWithSourceAndTargetType( source, TargetDto.class );
+ *
+ *     if ( source == null ) {
+ *         return null;
+ *     }
+ *
+ *     TargetDto targetDto = new TargetDto();
+ *
+ *     calledWithSourceAndTarget( source, targetDto );
+ *
+ *     // actual mapping code
+ *
+ *     return targetDto;
+ * }
+ * 
+ * 
+ * + * @author Andreas Gudian + * @see AfterMapping + * @see Context + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface BeforeMapping { + +} diff --git a/core/src/main/java/org/mapstruct/Builder.java b/core/src/main/java/org/mapstruct/Builder.java new file mode 100644 index 0000000000..449c4ac05c --- /dev/null +++ b/core/src/main/java/org/mapstruct/Builder.java @@ -0,0 +1,66 @@ +/* + * 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.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.util.Experimental; + +/** + * Configuration of builders, e.g. the name of the final build method. + * + *

+ * Example: Using builder + *

+ *

+ * // Mapper
+ * @Mapper
+ * public interface SimpleBuilderMapper {
+ *      @Mapping(target = "name", source = "fullName"),
+ *      @Mapping(target = "job", constant = "programmer"),
+ *      SimpleImmutablePerson toImmutable(SimpleMutablePerson source);
+ * }
+ * 
+ *

+ * // generates
+ * @Override
+ * public SimpleImmutablePerson toImmutable(SimpleMutablePerson source) {
+ *      // name method can be changed with parameter {@link #buildMethod()}
+ *      Builder simpleImmutablePerson = SimpleImmutablePerson.builder();
+ *      simpleImmutablePerson.name( source.getFullName() );
+ *      simpleImmutablePerson.age( source.getAge() );
+ *      simpleImmutablePerson.address( source.getAddress() );
+ *      simpleImmutablePerson.job( "programmer" );
+ *      // ...
+ * }
+ * 
+ * + * @author Filip Hrisafov + * + * @since 1.3 + */ +@Retention(RetentionPolicy.CLASS) +@Target({}) +@Experimental +public @interface Builder { + + /** + * The name of the build method that needs to be invoked on the builder to create the type to be build + * + * @return the method that needs to tbe invoked on the builder + */ + String buildMethod() default "build"; + + /** + * Toggling builders on / off. Builders are sometimes used solely for unit testing (fluent testdata) + * MapStruct will need to use the regular getters /setters in that case. + * + * @return when true, no builder patterns will be applied + */ + boolean disableBuilder() default false; +} 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 new file mode 100644 index 0000000000..0ea3ee7df5 --- /dev/null +++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java @@ -0,0 +1,99 @@ +/* + * 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 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
OptionOnly target set-s AvailableOnly target add- AvailableBoth set-s/add- AvailableNo set-s/add- AvailableExisting Target ({@code @TargetType})
{@link #ACCESSOR_ONLY}set-sget-sset-sget-sget-s
{@link #SETTER_PREFERRED}set-sadd-set-sget-sget-s
{@link #ADDER_PREFERRED}set-sadd-add-get-sget-s
{@link #TARGET_IMMUTABLE}set-sexceptionset-sexceptionset-s
+ * + * @author Sjaak Derksen + */ +public enum CollectionMappingStrategy { + + /** + * The setter of the target property will be used to propagate the value: + * {@code orderDto.setOrderLines(order.getOrderLines)}. + *

+ * If no setter is available but a getter method, this will be used, under the assumption it has been initialized: + * {@code orderDto.getOrderLines().addAll(order.getOrderLines)}. This will also be the case when using + * {@link MappingTarget} (updating existing instances). + */ + ACCESSOR_ONLY, + + /** + * If present, the setter of the target property will be used to propagate the value: + * {@code orderDto.setOrderLines(order.getOrderLines)}. + *

+ * If no setter but and adder method is present, that adder will be invoked for each element of the source + * collection: {@code order.addOrderLine(orderLine() )}. + *

+ * If neither a setter nor an adder method but a getter for the target property is present, that getter will be + * used, assuming it returns an initialized collection: If no setter is available, MapStruct will first look for an + * adder method before resorting to a getter. + */ + SETTER_PREFERRED, + + /** + * Identical to {@link #SETTER_PREFERRED}, only that adder methods will be preferred over setter methods, if both + * are present for a given collection-typed property. + */ + ADDER_PREFERRED, + + /** + * Identical to {@link #SETTER_PREFERRED}, however the target collection will not be cleared and accessed via + * addAll in case of updating existing bean instances, see: {@link MappingTarget}. + * + * Instead the target accessor (e.g. set) will be used on the target bean to set the collection. + */ + TARGET_IMMUTABLE; +} diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java new file mode 100644 index 0000000000..8abe2f817c --- /dev/null +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -0,0 +1,99 @@ +/* + * 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 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. + * 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 + * - 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
+ *   public static boolean isNotEmpty(String value) {
+ *      return value != null && !value.isEmpty();
+ *   }
+ * }
+ *
+ * @Mapper(uses = PresenceCheckUtils.class)
+ * public interface MovieMapper {
+ *
+ *     MovieDto map(Movie movie);
+ * }
+ * 
+ *

+ * The following implementation of {@code MovieMapper} will be generated: + * + *


+ * public class MovieMapperImpl implements MovieMapper {
+ *
+ *     @Override
+ *     public MovieDto map(Movie movie) {
+ *         if ( movie == null ) {
+ *             return null;
+ *         }
+ *
+ *         MovieDto movieDto = new MovieDto();
+ *
+ *         if ( PresenceCheckUtils.isNotEmpty( movie.getTitle() ) ) {
+ *             movieDto.setTitle( movie.getTitle() );
+ *         }
+ *
+ *         return movieDto;
+ *     }
+ * }
+ * 
+ *

+ * 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, 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/Context.java b/core/src/main/java/org/mapstruct/Context.java new file mode 100644 index 0000000000..58f606a038 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Context.java @@ -0,0 +1,181 @@ +/* + * 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; + +/** + * Marks a parameter of a method to be treated as mapping context. Such parameters are passed to other mapping + * methods, {@code @}{@link ObjectFactory} methods or {@code @}{@link BeforeMapping}/{@code @}{@link AfterMapping} + * methods when applicable and can thus be used in custom code. + *

+ * The type of an {@code @Context} parameter is also inspected for + * {@code @}{@link BeforeMapping}/{@code @}{@link AfterMapping} methods, which are called on the provided context + * parameter value if applicable. + *

+ * Note: no {@code null} checks are performed before calling before/after mapping methods or object + * factory methods on {@code @}{@link Context} annotated parameters. The caller needs to make sure that no {@code null} + * are passed in that case. + *

+ * For generated code to call a method that is declared with {@code @Context} parameters, the declaration of the mapping + * method being generated needs to contain at least those (or assignable) {@code @Context} parameters as well. MapStruct + * will not create new instances of missing {@code @Context} parameters nor will it pass {@code null} instead. + *

+ * Example 1: Using {@code @Context} parameters for passing data down to hand-written property mapping + * methods and {@code @}{@link BeforeMapping} methods: + * + *

+ * 
+ * // multiple @Context parameters can be added
+ * public abstract CarDto toCar(Car car, @Context VehicleRegistration context, @Context Locale localeToUse);
+ *
+ * protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) {
+ *     // manually implemented logic to translate the OwnerManual with the given Locale
+ * }
+ *
+ * @BeforeMapping
+ * protected void registerVehicle(Vehicle mappedVehicle, @Context VehicleRegistration context) {
+ *     context.register( mappedVehicle );
+ * }
+ *
+ * @BeforeMapping
+ * protected void notCalled(Vehicle mappedVehicle, @Context DifferentMappingContextType context) {
+ *     // not called, because no context parameter of type DifferentMappingContextType is available
+ *     // within toCar(Car, VehicleRegistration, Locale)
+ * }
+ *
+ * // generates:
+ *
+ * public CarDto toCar(Car car, VehicleRegistration context, Locale localeToUse) {
+ *     registerVehicle( car, context );
+ *
+ *     if ( car == null ) {
+ *         return null;
+ *     }
+ *
+ *     CarDto carDto = new CarDto();
+ *
+ *     carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), localeToUse );
+ *     // more generated mapping code
+ *
+ *     return carDto;
+ * }
+ * 
+ * 
+ *

+ * Example 2: Using an {@code @Context} parameter with a type that provides its own {@code @} + * {@link BeforeMapping} methods to handle cycles in Graph structures: + * + *

+ * 
+ * // type of the context parameter
+ * public class CyclicGraphContext {
+ *     private Map<Object, Object> knownInstances = new IdentityHashMap<>();
+ *
+ *     @BeforeMapping
+ *     public <T extends NodeDto> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
+ *         return (T) knownInstances.get( source );
+ *     }
+ *
+ *     @BeforeMapping
+ *     public void storeMappedInstance(Object source, @MappingTarget NodeDto target) {
+ *         knownInstances.put( source, target );
+ *     }
+ * }
+ *
+ * @Mapper
+ * public interface GraphMapper {
+ *     NodeDto toNodeDto(Node node, @Context CyclicGraphContext cycleContext);
+ * }
+ *
+ *
+ * // generates:
+ *
+ * public NodeDto toNodeDto(Node node, CyclicGraphContext cycleContext) {
+ *     NodeDto target = cycleContext.getMappedInstance( node, NodeDto.class );
+ *     if ( target != null ) {
+ *         return target;
+ *     }
+ *
+ *     if ( node == null ) {
+ *         return null;
+ *     }
+ *
+ *     NodeDto nodeDto = new NodeDto();
+ *
+ *     cycleContext.storeMappedInstance( node, nodeDto );
+ *
+ *     nodeDto.setParent( toNodeDto( node.getParent(), cycleContext ) );
+ *     List<NodeDto> list = nodeListToNodeDtoList( node.getChildren(), cycleContext );
+ *     if ( list != null ) {
+ *         nodeDto.setChildren( list );
+ *     }
+ *
+ *     // more mapping code
+ *
+ *     return nodeDto;
+ * }
+ * 
+ * 
+ *

+ * Example 3: Using {@code @Context} parameters for creating an entity object by calling an + * {@code @}{@link ObjectFactory} methods: + * + *

+ * 
+ * // type of the context parameter
+ * public class ContextObjectFactory {
+ *     @PersistenceContext(unitName = "my-unit")
+ *     private EntityManager em;
+ *
+ *     @ObjectFactory
+ *     public Valve create( String id ) {
+ *        Query query = em.createNamedQuery("Valve.findById");
+ *        query.setParameter("id", id);
+ *        Valve result = query.getSingleResult();
+ *        if ( result != null ) {
+ *            result = new Valve( id );
+ *        }
+ *        return result;
+ *     }
+ *
+ * }
+ *
+ * @Mapper
+ * public interface ContextWithObjectFactoryMapper {
+ *     Valve map(ValveDto dto, @Context ContextObjectFactory factory, String id);
+ * }
+ *
+ *
+ * // generates:
+ * public class ContextWithObjectFactoryMapperImpl implements ContextWithObjectFactoryMapper {
+ *
+ *   @Override
+ *   public Valve map(ValveDto dto, ContextObjectFactory factory, String id) {
+ *       if ( dto == null ) {
+ *           return null;
+ *       }
+ *
+ *       Valve valve = factory.create( id );
+ *
+ *       valve.setOneWay( dto.isOneWay() );
+ *
+ *       return valve;
+ *   }
+ * }
+ * 
+ * 
+ * @author Andreas Gudian + * @since 1.2 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface Context { + +} diff --git a/core-common/src/main/java/org/mapstruct/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java similarity index 80% rename from core-common/src/main/java/org/mapstruct/DecoratedWith.java rename to core/src/main/java/org/mapstruct/DecoratedWith.java index 4dd6e66ee8..3db27b2a0a 100644 --- a/core-common/src/main/java/org/mapstruct/DecoratedWith.java +++ b/core/src/main/java/org/mapstruct/DecoratedWith.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct; @@ -35,9 +22,7 @@ *

* NOTE: This annotation is not supported for the component model {@code cdi}. Use CDI's own * {@code @Decorator} feature instead. - *

- * NOTE: The decorator feature when used with component model {@code jsr330} is considered experimental - * and it may change in future releases. + *

*

Examples

*

* For the examples below, consider the following mapper declaration: @@ -118,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): * @@ -155,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 new file mode 100644 index 0000000000..375f969b01 --- /dev/null +++ b/core/src/main/java/org/mapstruct/EnumMapping.java @@ -0,0 +1,156 @@ +/* + * 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; + +/** + * Configured the mapping between two value types. + *

Example: Using a suffix for enums

+ *

+ * public enum CheeseType {
+ *     BRIE,
+ *     ROQUEFORT
+ * }
+ *
+ * public enum CheeseTypeSuffixed {
+ *     BRIE_TYPE,
+ *     ROQUEFORT_TYPE
+ * }
+ *
+ * @Mapper
+ * public interface CheeseMapper {
+ *
+ *     @EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")
+ *     CheeseTypeSuffixed map(Cheese cheese);
+ *
+ *     @InheritInverseConfiguration
+ *     Cheese map(CheeseTypeSuffixed cheese);
+ *
+ * }
+ * 
+ *

+ * // generates
+ * public class CheeseMapperImpl implements CheeseMapper {
+ *
+ *     @Override
+ *     public CheeseTypeSuffixed map(Cheese cheese) {
+ *         if ( cheese == null ) {
+ *             return null;
+ *         }
+ *
+ *         CheeseTypeSuffixed cheeseTypeSuffixed;
+ *
+ *         switch ( cheese ) {
+ *             case BRIE:
+ *                 cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;
+ *                 break;
+ *             case ROQUEFORT:
+ *                 cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;
+ *                 break;
+ *             default:
+ *                 throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
+ *         }
+ *
+ *         return cheeseTypeSuffixed;
+ *     }
+ *
+ *     @Override
+ *     public Cheese map(CheeseTypeSuffixed cheese) {
+ *         if ( cheese == null ) {
+ *             return null;
+ *         }
+ *
+ *         CheeseType cheeseType;
+ *
+ *         switch ( cheese ) {
+ *             case BRIE_TYPE:
+ *                 cheeseType = CheeseType.BRIE;
+ *                 break;
+ *             case ROQUEFORT_TYPE:
+ *                 cheeseType = CheeseType.ROQUEFORT;
+ *                 break;
+ *             default:
+ *                 throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
+ *         }
+ *
+ *         return cheeseType;
+ *     }
+ * }
+ * 
+ * + * @author Filip Hrisafov + * @since 1.4 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface EnumMapping { + + /** + * Specifies the name transformation strategy that should be used for implicit mapping between enums. + * Known strategies are: + *
    + *
  • {@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a + * suffix to the source enum
  • + *
  • {@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
  • + *
  • {@link MappingConstants#STRIP_PREFIX_TRANSFORMATION} - strips the given {@link #configuration()} from + * the start of the source enum
  • + *
  • + * {@link MappingConstants#CASE_TRANSFORMATION} - applies the given {@link #configuration()} case + * transformation to the source enum. Supported configurations are: + *
      + *
    • upper - Performs upper case transformation to the source enum
    • + *
    • lower - Performs lower case transformation to the source enum
    • + *
    • + * capital - Performs capitalisation of the first character of every word in the source enum + * and everything else to lower case. A word is split by "_". + *
    • + *
    + *
  • + *
+ * + * It is possible to use custom name transformation strategies by implementing the {@code + * EnumTransformationStrategy} SPI. + * + * @return the name transformation strategy + */ + String nameTransformationStrategy() default ""; + + /** + * The configuration that should be passed on the appropriate name transformation strategy. + * e.g. a suffix that should be applied to the source enum when doing name based mapping. + * + * @return the configuration to use + */ + String configuration() default ""; + + /** + * Exception that should be thrown by the generated code if no mapping matches. + * If no exception is configured, the exception given via {@link MapperConfig#unexpectedValueMappingException()} or + * {@link Mapper#unexpectedValueMappingException()} will be used, using {@link IllegalArgumentException} by default. + * + *

+ * Note: + *

    + *
  • + * The defined exception should at least have a constructor with a {@link String} parameter. + *
  • + *
  • + * If the defined exception is a checked exception then the enum mapping methods should have that exception + * in the throws clause. + *
  • + *
+ * + * @return the exception that should be used in the generated code + */ + Class unexpectedValueMappingException() default IllegalArgumentException.class; +} 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)
+ * }
+ * 
+ *

+ * // generates:
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *    humanDto.setFullName( human.getFullName() );
+ *    // ...
+ * }
+ * 
+ * + * @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. + * + *

These two examples are equal. + *

+ *

+ * // before Java 8
+ * @Mapper
+ * public interface MyMapper {
+ *     @IgnoredList({
+ *         @Ignored(targets = { "firstProperty" } ),
+ *         @Ignored(targets = { "secondProperty" } )
+ *     })
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ *

+ * // Java 8 and later
+ * @Mapper
+ * public interface MyMapper {
+ *     @Ignored(targets = { "firstProperty" } ),
+ *     @Ignored(targets = { "secondProperty" } )
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ * + * @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/InheritConfiguration.java b/core/src/main/java/org/mapstruct/InheritConfiguration.java new file mode 100644 index 0000000000..0d1bf0b38f --- /dev/null +++ b/core/src/main/java/org/mapstruct/InheritConfiguration.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; + +/** + * Advises the code generator to apply the configuration (as given via {@link Mapping}, {@link IterableMapping} etc.) + * from another mapping method (declared on the same mapper type) or prototype method (declared on a mapper config class + * referenced via {@link Mapper#config()}) to the annotated method as well. + *

+ * If no method can be identified unambiguously as configuration source (i.e. several candidate methods with matching + * source and target type exist), the name of the method to inherit from must be specified via {@link #name()}. + *

+ * {@link Mapping#expression()}, {@link Mapping#constant()}, {@link Mapping#defaultExpression()} and + * {@link Mapping#defaultValue()} are not inverse inherited + * + *

+ * A typical use case is annotating an update method so it inherits all mappings from a corresponding "standard" mapping + * method: + * + *

+ * 
+ * @Mappings({
+ *     @Mapping(target="make", source="brand"),
+ *     @Mapping(target="seatCount", source="numberOfSeats")
+ * })
+ * CarDto carToCarDto(Car car);
+ *
+ * @InheritConfiguration
+ * void updateCarDto(Car car, @MappingTarget CarDto carDto);
+ * 
+ * 
+ * + * @author Sjaak Derksen + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface InheritConfiguration { + + /** + * The name of the mapping method to inherit the mappings from. Needs only to be specified in case more than one + * method with matching source and target type exists. + * + * @return The name of the mapping method to inherit the mappings from. + */ + String name() default ""; +} diff --git a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java new file mode 100644 index 0000000000..b659b7f37a --- /dev/null +++ b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * Advises the code generator to apply all the {@link Mapping}s from an inverse mapping method to the annotated method + * as well. An inverse mapping method is a method which has the annotated method's source type as target type (return + * type or indicated through a parameter annotated with {@link MappingTarget}) and the annotated method's target type as + * source type. + *

+ * Any mappings given on the annotated method itself are added to those mappings inherited from the inverse method. In + * case of a conflict local mappings take precedence over inherited mappings. + *

+ * If more than one matching inverse method exists, the name of the method to inherit the configuration from must be + * specified via {@link #name()} + *

+ * {@link Mapping#expression()}, {@link Mapping#constant()}, {@link Mapping#defaultExpression()} and + * {@link Mapping#defaultValue()} are not inverse inherited + * + *

+ * Examples + *

+ *

+ * @Mapper
+ * public interface HumanMapper {
+ *      Human toHuman(HumanDto humanDto);
+ *      @InheritInverseConfiguration
+ *      HumanDto toHumanDto(Human human);
+ * }
+ * 
+ *

+ * // generates
+ * public class HumanMapperImpl implements HumanMapper {
+ *      @Override
+ *      public Human toHuman(HumanDto humanDto) {
+ *          if ( humanDto == null ) {
+ *              return null;
+ *           }
+ *          Human human = new Human();
+ *          human.setName( humanDto.getName() );
+ *          return human;
+ *      }
+ *      @Override
+ *      public HumanDto toHumanDto(Human human) {
+ *          if ( human == null ) {
+ *              return null;
+ *          }
+ *          HumanDto humanDto = new HumanDto();
+ *          humanDto.setName( human.getName() );
+ *          return humanDto;
+ *      }
+ * }
+ * 
+ * + *

+ * @Mapper
+ * public interface CarMapper {
+ *
+ * @Mapping( target = "seatCount", source = "numberOfSeats")
+ * @Mapping( target = "enginePower", source = "engineClass", ignore=true) // NOTE: source specified as well
+ * CarDto carToDto(Car car);
+ *
+ * @InheritInverseConfiguration
+ * @Mapping(target = "numberOfSeats", ignore = true)
+ * // no need to specify a mapping with ignore for "engineClass": specifying source above will assume
+ * Car carDtoToCar(CarDto carDto);
+ * }
+ * 
+ * @author Sjaak Derksen + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface InheritInverseConfiguration { + + /** + * 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. + */ + String name() default ""; +} diff --git a/core/src/main/java/org/mapstruct/InjectionStrategy.java b/core/src/main/java/org/mapstruct/InjectionStrategy.java new file mode 100644 index 0000000000..f5029e2246 --- /dev/null +++ b/core/src/main/java/org/mapstruct/InjectionStrategy.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; + +/** + * Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and + * JSR330 / Jakarta. + * + * @author Kevin Grüneberg + * @author Lucas Resch + */ +public enum InjectionStrategy { + + /** Annotations are written on the field **/ + FIELD, + + /** Annotations are written on the 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 new file mode 100644 index 0000000000..d644dfe03b --- /dev/null +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -0,0 +1,159 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.mapstruct.control.MappingControl; + +/** + * Configures the mapping between two iterable like types, e.g. {@code List} and {@code List}. + * + * + *

Note: either {@link #dateFormat()}, {@link #elementTargetType()} or {@link #qualifiedBy() } + * must be specified

+ * + *

+ * Example: Convert List<Float> to List<String> + *

+ *

+ * @Mapper
+ * public interface FloatToStringMapper {
+ *      @IterableMapping( numberFormat = "##.00" )
+ *      List<String> sourceToTarget(List<Float> source);
+ * }
+ * 
+ *

+ * // generates
+ * public class FloatToStringMapperImpl implements FloatToStringMapper {
+ *      @Override
+ *      public List<String> sourceToTarget(List<Float> source) {
+ *          List<String> list = new ArrayList<String>( source.size() );
+ *          for ( Float float1 : source ) {
+ *              list.add( new DecimalFormat( "##.00" ).format( float1 ) );
+ *          }
+ *     // ...
+ *      }
+ * }
+ * 
+ * + * Supported mappings are: + * + * + * @author Gunnar Morling + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface IterableMapping { + + /** + * 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. + * + * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. + * + * @return the qualifiers + * @see Qualifier + */ + Class[] qualifiedBy() default { }; + + /** + * String-based form of qualifiers; When looking for a suitable mapping method to map this iterable mapping method's + * element type, 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 { }; + + /** + * Specifies the type of the element to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * + * @return the elementTargetType to select + */ + Class elementTargetType() default void.class; + + + /** + * The strategy to be applied when {@code null} is passed as source value to this iterable mapping. If no + * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or + * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} + * by default. + * + * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. + */ + NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * Allows detailed control over the mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class elementMappingControl() default MappingControl.class; + +} 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"
+ * )
+ * 
+ * + *

+ * will generate: + *

+ * + *

+ * /**
+ * * This is the description
+ * *
+ * * @author author1
+ * * @author author2
+ * *
+ * * @deprecated Use {@link OtherMapper} instead
+ * * @since 0.1
+ * */
+ * 
+ * + *

+ * The entire Javadoc comment block can be passed directly: + *

+ *

+ * @Javadoc("This is the description\n"
+ *            + "\n"
+ *            + "@author author1\n"
+ *            + "@author author2\n"
+ *            + "\n"
+ *            + "@deprecated Use {@link OtherMapper} instead\n"
+ *            + "@since 0.1\n"
+ * )
+ * 
+ * + *

+ * // 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 new file mode 100644 index 0000000000..093099cf5a --- /dev/null +++ b/core/src/main/java/org/mapstruct/MapMapping.java @@ -0,0 +1,227 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.mapstruct.control.MappingControl; + +/** + * Configures the mapping between two map types, e.g. Map<String, String> and Map<Long, Date>. + * + *

+ * Example: + *

+ *

+ * @Mapper
+ * public interface SimpleMapper {
+ *       @MapMapping(valueDateFormat = "dd.MM.yyyy")
+ *       Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
+ * }
+ * 
+ *

+ * // generates
+ * public class SimpleMapperImpl implements SimpleMapper {
+ *      @Override
+ *      public Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source) } {
+ *          Map<String, String> map = new HashMap<String, String>(); }
+ *          for ( java.util.Map.Entry<Long, Date> entry : source.entrySet() ) } {
+ *              String key = new DecimalFormat( "" ).format( entry.getKey() );
+ *              String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
+ *              map.put( key, value );
+ *          }
+ *          // ...
+ *      }
+ * }
+ * 
+ * + *

NOTE: at least one element needs to be specified

+ * + * @author Gunnar Morling + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface MapMapping { + + /** + * 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 ""; + + /** + * 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' + * error. + * + * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. + * + * @return the qualifiers + * @see Qualifier + */ + Class[] keyQualifiedBy() default { }; + + /** + * String-based form of qualifiers; When looking for a suitable mapping method to map this map mapping method's key + * type, 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 #keyQualifiedBy() + * @see Named + */ + String[] keyQualifiedByName() default { }; + + + /** + * A value qualifier can be specified to aid the selection process of a suitable mapper for the values in the map. + * This is useful in case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping + * methods found' error. + *

+ * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. + * + * @return the qualifiers + * @see Qualifier + */ + Class[] valueQualifiedBy() default { }; + + /** + * String-based form of qualifiers; When looking for a suitable mapping method to map this map mapping method's + * value type, 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 #valueQualifiedBy() + * @see Named + */ + String[] valueQualifiedByName() default { }; + + /** + * Specifies the type of the key to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * + * + * @return the resultType to select + */ + Class keyTargetType() default void.class; + + /** + * Specifies the type of the value to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * + * + * @return the resultType to select + */ + Class valueTargetType() default void.class; + + + /** + * The strategy to be applied when {@code null} is passed as source value to this map mapping. If no + * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or + * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} + * by default. + * + * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping. + */ + NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * Allows detailed control over the key mapping process. + * + * @return the mapping control + * + * @since 1.4 + + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class keyMappingControl() default MappingControl.class; + + + /** + * Allows detailed control over the value mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class valueMappingControl() default MappingControl.class; + +} diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java new file mode 100644 index 0000000000..c502dfe8a9 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -0,0 +1,401 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +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 + * MapStruct. + * + *

+ * Example 1: Creating mapper + *

+ *

+ * @Mapper
+ * public interface CarMapper {
+ *     CarDto toCarDto(Car source);
+ * }
+ * 
+ *

+ * Example 2: Use additional mappers with parameters {@link #uses()}, {@link #componentModel()} + * and {@link #injectionStrategy()} + *

+ *

+ * // we have MarkMapper (map field "mark" to field "name" to upper case)
+ * @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
+ * public class MarkMapper {
+ *     public String mapMark(String mark) {
+ *         return mark.toUpperCase();
+ *     }
+ * }
+ * // we have CarMapper
+ * @Mapper(
+ *      componentModel = MappingConstants.ComponentModel.SPRING,
+ *      uses = MarkMapper.class,
+ *      injectionStrategy = InjectionStrategy.CONSTRUCTOR)
+ * public interface CarMapper {
+ *     @Mapping(target = "name", source = "mark")
+ *     CarDto convertMap(CarEntity carEntity);
+ * }
+ * 
+ *

+ * // generates
+ * @Component
+ * public class CarMapperImpl implements CarMapper {
+ *     private final MarkMapper markMapper;
+ *     @Autowired
+ *     public CarMapperImpl(MarkMapper markMapper) {
+ *         this.markMapper = markMapper;
+ *     }
+ *     @Override
+ *     public CarDto convertMap(CarEntity carEntity) {
+ *         if ( carEntity == null ) {
+ *             return null;
+ *         }
+ *         CarDto carDto = new CarDto();
+ *         carDto.setName( markMapper.mapMark( carEntity.getMark() ) );
+ *         return carDto;
+ *     }
+ * }
+ * 
+ * + * @author Gunnar Morling + * @see Javadoc + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface Mapper { + + /** + * Other mapper types used by this mapper. May be hand-written classes or other mappers generated by MapStruct. No + * cycle between generated mapper classes must be created. + * + * @return The mapper types used by this mapper. + */ + Class[] uses() default { }; + + /** + * Additional types for which an import statement is to be added to the generated mapper implementation class. + * This allows to refer to those types from within mapping expressions given via {@link Mapping#expression()}, + * {@link Mapping#defaultExpression()} or using + * their simple name rather than their fully-qualified name. + * + * @return classes to add in the imports of the generated implementation. + */ + Class[] imports() default { }; + + /** + * How unmapped properties of the source type of a mapping should be + * reported. The method overrides an unmappedSourcePolicy set in a central + * configuration set by {@link #config() } + * + * @return The reporting policy for unmapped source properties. + * + * @since 1.3 + */ + ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE; + + /** + * How unmapped properties of the target type of a mapping should be + * reported. The method overrides an unmappedTargetPolicy set in a central + * configuration set by {@link #config() } + * + * @return The reporting policy for unmapped target properties. + */ + ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN; + + /** + * How lossy (narrowing) conversion, for instance long to integer should be + * reported. The method overrides an typeConversionPolicy set in a central + * configuration set by {@link #config() } + * + * @since 1.3 + * + * @return The reporting policy for unmapped target properties. + */ + ReportingPolicy typeConversionPolicy() default ReportingPolicy.IGNORE; + + /** + * Specifies the component model to which the generated mapper should + * adhere. Supported values are + *
    + *
  • {@code default}: the mapper uses no component model, instances are + * typically retrieved via {@link Mappers#getMapper(Class)}
  • + *
  • + * {@code cdi}: the generated mapper is an application-scoped CDI bean and + * can be retrieved via {@code @Inject}
  • + *
  • + * {@code spring}: the generated mapper is a Spring bean and + * 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}. + * 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() } + * + * @return The component model for the generated mapper. + */ + String componentModel() default MappingConstants.ComponentModel.DEFAULT; + + /** + * Specifies the name of the implementation class. The {@code } will be replaced by the + * interface/abstract class name. + *

+ * Defaults to postfixing the name with {@code Impl}: {@code Impl} + * + * @return The implementation name. + * @see #implementationPackage() + */ + String implementationName() default "Impl"; + + /** + * Specifies the target package for the generated implementation. The {@code } will be replaced by the + * interface's or abstract class' package. + *

+ * Defaults to using the same package as the mapper interface/abstract class + * + * @return the implementation package. + * @see #implementationName() + */ + String implementationPackage() default ""; + + /** + * A class annotated with {@link MapperConfig} which should be used as configuration template. Any settings given + * via {@link Mapper} will take precedence over the settings from the referenced configuration source. The list of + * referenced mappers will contain all mappers given via {@link Mapper#uses()} and {@link MapperConfig#uses()}. + * + * @return A class which should be used as configuration template. + */ + Class config() default void.class; + + /** + * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans + * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder + * method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}). + *

+ * Any setting given for this attribute will take precedence over {@link MapperConfig#collectionMappingStrategy()}, + * if present. + * + * @return The strategy applied when propagating the value of collection-typed properties. + */ + CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to the methods of this mapper. + * If no strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} will be + * applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. + */ + 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, + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @since 1.3 + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default + NullValuePropertyMappingStrategy.SET_TO_NULL; + + /** + * The strategy to use for applying method-level configuration annotations of prototype methods in the interface + * specified with {@link #config()}. Annotations that can be inherited are for example {@link Mapping}, + * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. + *

+ * If no strategy is configured, the strategy given via {@link MapperConfig#mappingInheritanceStrategy()} will be + * applied, using {@link MappingInheritanceStrategy#EXPLICIT} as default. + * + * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface + * specified with {@link #config()}. + */ + MappingInheritanceStrategy mappingInheritanceStrategy() default MappingInheritanceStrategy.EXPLICIT; + + /** + * Determines when to include a null check on the source property value of a bean mapping. + * + * Can be overridden by the one on {@link MapperConfig}, {@link BeanMapping} or {@link Mapping}. + * + * @return strategy how to do null checking + */ + 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 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. + * + * If no strategy is configured, {@link InjectionStrategy#FIELD} will be used as default. + * + * @return strategy how to inject + */ + InjectionStrategy injectionStrategy() default InjectionStrategy.FIELD; + + /** + * If MapStruct could not find another mapping method or apply an automatic conversion it will try to generate a + * sub-mapping method between the two beans. If this property is set to {@code true} MapStruct will not try to + * automatically generate sub-mapping methods. + *

+ * 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 + * github.com/mapstruct/mapstruct to share what problem you + * are facing with the automatic sub-mapping generation. + * + * @return whether the automatic generation of sub-mapping methods is disabled + * + * @since 1.2 + */ + boolean disableSubMappingMethodsGeneration() default false; + + /** + * The information that should be used for the builder mappings. This can be used to define custom build methods + * for the builder strategy that one uses. + * + * If no builder is defined the builder given via {@link MapperConfig#builder()} will be applied. + * + *

+ * NOTE: In case no builder is defined here, in {@link BeanMapping} or {@link MapperConfig} and there is a single + * build method, then that method would be used. + *

+ * If the builder is defined and there is a single method that does not match the name of the build method then + * a compile error will occur + * + * @return the builder information + * + * @since 1.3 + */ + Builder builder() default @Builder; + + /** + * Allows detailed control over the mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class mappingControl() default MappingControl.class; + + /** + * Exception that should be thrown by the generated code if no mapping matches for enums. + * If no exception is configured, the exception given via {@link MapperConfig#unexpectedValueMappingException()} + * will be used, using {@link IllegalArgumentException} by default. + * + *

+ * Note: + *

    + *
  • + * The defined exception should at least have a constructor with a {@link String} parameter. + *
  • + *
  • + * If the defined exception is a checked exception then the enum mapping methods should have that exception + * in the throws clause. + *
  • + *
+ * + * @return the exception that should be used in the generated code + * + * @since 1.4 + */ + Class 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 new file mode 100644 index 0000000000..a81e3e8d4f --- /dev/null +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -0,0 +1,371 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +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 + * between several mapper classes. + *

+ * Generally, any settings given via {@link Mapper} take precedence over the settings given via the referenced + * {@code MapperConfig}. The lists of referenced mappers given via {@link Mapper#uses()} and + * {@link MapperConfig#uses()} will be merged. + *

+ * Additionally, mapper configuration classes may declare one more prototype mapping methods. These methods are + * not meant to be invoked themselves (no implementation will generated for mapper config classes), but serve as + * configuration template for mapping methods declared by actual mapper classes. Depending on the configured + * {@link #mappingInheritanceStrategy()}, the configuration can be inherited either explicitly using + * {@link InheritConfiguration} or {@link InheritInverseConfiguration}, or automatically in case all source and target + * types are assignable. + *

+ * + *

+ * Example: + *

+ *

+ * // create config
+ * @MapperConfig(
+ *     uses = CustomMapperViaMapperConfig.class,
+ *     unmappedTargetPolicy = ReportingPolicy.ERROR
+ * )
+ * public interface CentralConfig {
+ * }
+ * 
+ *

+ * // use config
+ * @Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
+ * public interface SourceTargetMapper {
+ *   // ...
+ * }
+ * 
+ *

+ * // result after applying CentralConfig
+ * @Mapper(
+ *     uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
+ *     unmappedTargetPolicy = ReportingPolicy.ERROR
+ * )
+ * public interface SourceTargetMapper {
+ *    // ...
+ * }
+ * 
+ * + * @author Sjaak Derksen + * @see Mapper#config() + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface MapperConfig { + + /** + * The mapper types used by this mapper. + * + * @return The mapper types used by this mapper. + */ + Class[] uses() default { }; + + /** + * Additional types for which an import statement is to be added to the generated mapper implementation class. + * This allows to refer to those types from within mapping expressions given via {@link Mapping#expression()}, + * {@link Mapping#defaultExpression()} or using + * their simple name rather than their fully-qualified name. + * + * @return classes to add in the imports of the generated implementation. + * + * @since 1.4 + */ + Class[] imports() default { }; + + /** + * How unmapped properties of the source type of a mapping should be + * reported. + * + * @return The reporting policy for unmapped source properties. + * + * @since 1.3 + */ + ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE; + + /** + * How unmapped properties of the target type of a mapping should be + * reported. + * + * @return The reporting policy for unmapped target properties. + */ + ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN; + + /** + * How lossy (narrowing) conversion, for instance: long to integer should be + * reported. + * + * @since 1.3 + * + * @return The reporting policy for type conversion. + */ + ReportingPolicy typeConversionPolicy() default ReportingPolicy.IGNORE; + + /** + * Specifies the component model to which the generated mapper should + * adhere. Supported values are + *
    + *
  • {@code default}: the mapper uses no component model, instances are + * typically retrieved via {@link Mappers#getMapper(Class)}
  • + *
  • + * {@code cdi}: the generated mapper is an application-scoped CDI bean and + * can be retrieved via {@code @Inject}
  • + *
  • + * {@code spring}: the generated mapper is a Spring bean and + * 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}. + * 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. + */ + String componentModel() default MappingConstants.ComponentModel.DEFAULT; + + /** + * Specifies the name of the implementation class. The {@code } will be replaced by the + * interface/abstract class name. + *

+ * Defaults to postfixing the name with {@code Impl}: {@code Impl} + * + * @return The implementation name. + * @see #implementationPackage() + */ + String implementationName() default "Impl"; + + /** + * Specifies the target package for the generated implementation. The {@code } will be replaced by the + * interface's or abstract class' package. + *

+ * Defaults to using the same package as the mapper interface/abstract class + * + * @return the implementation package. + * @see #implementationName() + */ + String implementationPackage() default ""; + + /** + * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans + * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder + * method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}). + * + * @return The strategy applied when propagating the value of collection-typed properties. + */ + CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to mapping methods. If no + * strategy is configured, {@link NullValueMappingStrategy#RETURN_NULL} will be used by default. + * + * @return The strategy to be applied when {@code null} is passed as source value to mapping methods. + */ + 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. + * + * @since 1.3 + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default + NullValuePropertyMappingStrategy.SET_TO_NULL; + + /** + * The strategy to use for applying method-level configuration annotations of prototype methods in the interface + * annotated with this annotation. Annotations that can be inherited are for example {@link Mapping}, + * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. + *

+ * If no strategy is configured, {@link MappingInheritanceStrategy#EXPLICIT} will be used as default. + * + * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface + * annotated with this annotation. + */ + MappingInheritanceStrategy mappingInheritanceStrategy() + default MappingInheritanceStrategy.EXPLICIT; + + /** + * Determines when to include a null check on the source property value of a bean mapping. + * + * Can be overridden by the one on {@link Mapper}, {@link BeanMapping} or {@link Mapping}. + * + * @return strategy how to do null checking + */ + 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 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. + * + * Can be overridden by the one on {@link Mapper}. + * + * If no strategy is configured, {@link InjectionStrategy#FIELD} will be used as default. + * + * @return strategy how to inject + */ + InjectionStrategy injectionStrategy() default InjectionStrategy.FIELD; + + /** + * If MapStruct could not find another mapping method or apply an automatic conversion it will try to generate a + * sub-mapping method between the two beans. If this property is set to {@code true} MapStruct will not try to + * automatically generate sub-mapping methods. + *

+ * Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()} + *

+ * Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at + * mapstruct.org or + * github.com/mapstruct/mapstruct to share what problem you + * are facing with the automatic sub-mapping generation. + * + * @return whether the automatic generation of sub-mapping methods is disabled + * + * @since 1.2 + */ + boolean disableSubMappingMethodsGeneration() default false; + + /** + * The information that should be used for the builder mappings. This can be used to define custom build methods + * for the builder strategy that one uses. + * + *

+ * Can be overridden by {@link MapperConfig#builder()}. + * + *

+ * NOTE: In case no builder is defined here, in {@link BeanMapping} or {@link Mapper} and there is a single + * build method, then that method would be used. + *

+ * If the builder is defined and there is a single method that does not match the name of the build method then + * a compile error will occur + * + * @return the builder information + * + * @since 1.3 + */ + Builder builder() default @Builder; + + /** + * Allows detailed control over the mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class mappingControl() default MappingControl.class; + + /** + * Exception that should be thrown by the generated code if no mapping matches for enums. + * If no exception is configured, {@link IllegalArgumentException} will be used by default. + * + *

+ * Note: + *

    + *
  • + * The defined exception should at least have a constructor with a {@link String} parameter. + *
  • + *
  • + * If the defined exception is a checked exception then the enum mapping methods should have that exception + * in the throws clause. + *
  • + *
+ * + * @return the exception that should be used in the generated code + * + * @since 1.4 + */ + Class 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 4a902fe704..c2a6172e67 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -1,50 +1,147 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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 java.text.SimpleDateFormat; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.Date; +import org.mapstruct.control.MappingControl; + +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 * {@link #source()}, {@link #expression()} or {@link #constant()} can be specified to define the property source. + *

*

* In addition, the attributes {@link #dateFormat()} and {@link #qualifiedBy()} may be used to further define the * mapping. + *

* *

- * IMPORTANT NOTE: the enum mapping capability is deprecated and replaced by {@link ValueMapping} it - * will be removed in subsequent versions. + * Example 1: Implicitly mapping fields with the same name: + *

+ *

+ * // Both classes HumanDto and Human have property with name "fullName"
+ * // properties with the same name will be mapped implicitly
+ * @Mapper
+ * public interface HumanMapper {
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates:
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *    humanDto.setFullName( human.getFullName() );
+ *    // ...
+ * }
+ * 
+ * + *

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 #target()}
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Mapping(source="companyName", target="company")
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates:
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *     humanDto.setCompany( human.getCompanyName() );
+ *      // ...
+ * }
+ * 
+ *

+ * Example 3: Mapping with expression + * IMPORTANT NOTE: Now it works only for Java + *

+ *

+ * // We need map Human.name to HumanDto.countNameSymbols.
+ * // we can use {@link #expression()} for it
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Mapping(target="countNameSymbols", expression="java(human.getName().length())")
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates:
+ *@Override
+ * public HumanDto toHumanDto(Human human) {
+ *    humanDto.setCountNameSymbols( human.getName().length() );
+ *    //...
+ * }
+ * 
+ *

+ * Example 4: Mapping to constant + *

+ *

+ * // We need map HumanDto.name to string constant "Unknown"
+ * // we can use {@link #constant()} for it
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Mapping(target="name", constant="Unknown")
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *   humanDto.setName( "Unknown" );
+ *   // ...
+ * }
+ * 
+ *

+ * Example 5: Mapping with default value + *

+ *

+ * // We need map Human.name to HumanDto.fullName, but if Human.name == null, then set value "Somebody"
+ * // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Mapping(source="name", target="fullName", defaultValue="Somebody")
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *    if ( human.getName() != null ) {
+ *       humanDto.setFullName( human.getName() );
+ *    }
+ *    else {
+ *       humanDto.setFullName( "Somebody" );
+ *    }
+ *   // ...
+ * }
+ * 
* * @author Gunnar Morling */ + +@Repeatable(Mappings.class) @Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Mapping { /** @@ -78,25 +175,70 @@ /** * 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 ""; /** - * A constant {@link String} based on which the specified target property is to be set. If the designated target - * property is not of type {@code String}, the value will be converted by applying a matching conversion method or - * built-in conversion. + * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}. *

- * This attribute can not be used together with {@link #source()}, {@link #defaultValue()} or {@link #expression()}. + * 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. + *

+ * When the designated target property is of type: + *

+ *
    + *
  1. primitive or boxed (e.g. {@code java.lang.Long}). + *

    + * MapStruct checks whether the primitive can be assigned as valid literal to the primitive or boxed type. + *

    + *
      + *
    • + * If possible, MapStruct assigns as literal. + *
    • + *
    • + * If not possible, MapStruct will try to apply a user defined mapping method. + *
    • + *
    + *
  2. + *
  3. 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. + *

  4. + *
+ *

+ * 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()}. * * @return A constant {@code String} constant specifying the value for the designated target property */ @@ -121,17 +263,51 @@ * Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be * imported via {@link Mapper#imports()}. *

- * This attribute can not be used together with {@link #source()}, {@link #defaultValue()} or {@link #constant()}. + * This attribute can not be used together with {@link #source()}, {@link #defaultValue()}, + * {@link #defaultExpression()}, {@link #qualifiedBy()}, {@link #qualifiedByName()} or {@link #constant()}. * * @return An expression specifying the value for the designated target property */ String expression() default ""; + /** + * A defaultExpression {@link String} based on which the specified target property is to be set + * if and only if the specified source property is null. + *

+ * Currently, Java is the only supported "expression language" and expressions must be given in form of Java + * expressions using the following format: {@code java()}. For instance the mapping: + *


+     * @Mapping(
+     *     target = "someProp",
+     *     defaultExpression = "java(new TimeAndFormat( s.getTime(), s.getFormat() ))"
+     * )
+     * 
+ *

+ * will cause the following target property assignment to be generated: + *

+ * {@code targetBean.setSomeProp( new TimeAndFormat( s.getTime(), s.getFormat() ) )}. + *

+ * Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be + * imported via {@link Mapper#imports()}. + *

+ * This attribute can not be used together with {@link #expression()}, {@link #defaultValue()} + * or {@link #constant()}. + * + * @return An expression specifying a defaultValue for the designated target property if the designated source + * property is null + * + * @since 1.3 + */ + String defaultExpression() default ""; + /** * 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 */ @@ -141,8 +317,11 @@ * 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 */ Class[] qualifiedBy() default { }; @@ -154,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() @@ -161,6 +342,72 @@ */ String[] qualifiedByName() default { }; + /** + * A qualifier can be specified to aid the selection process of a suitable presence check method. + * This is useful in case multiple presence check methods qualify and thus would result in an + * 'Ambiguous presence check methods found' error. + * A qualifier is a custom annotation and can be placed on a hand written mapper class or a method. + * This is similar to the {@link #qualifiedBy()}, but it is only applied for {@link Condition} methods. + * + * @return the qualifiers + * @see Qualifier + * @see #qualifiedBy() + * @since 1.5 + */ + Class[] conditionQualifiedBy() default { }; + + /** + * String-based form of qualifiers for condition / presence check methods; + * When looking for a suitable presence check 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. + * + * This is similar like {@link #qualifiedByName()} but it is only applied for {@link Condition} methods. + *

+ * 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 #conditionQualifiedBy() + * @see #qualifiedByName() + * @see Named + * @since 1.5 + */ + String[] conditionQualifiedByName() default { }; + + /** + * A conditionExpression {@link String} based on which the specified property is to be checked + * whether it is present or not. + *

+ * Currently, Java is the only supported "expression language" and expressions must be given in form of Java + * expressions using the following format: {@code java()}. For instance the mapping: + *


+     * @Mapping(
+     *     target = "someProp",
+     *     conditionExpression = "java(s.getAge() < 18)"
+     * )
+     * 
+ *

+ * will cause the following target property assignment to be generated: + *


+     *     if (s.getAge() < 18) {
+     *         targetBean.setSomeProp( s.getSomeProp() );
+     *     }
+     * 
+ *

+ * Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be + * imported via {@link Mapper#imports()}. + *

+ * This attribute can not be used together with {@link #expression()} or {@link #constant()}. + * + * @return An expression specifying a condition check for the designated property + * + * @since 1.5 + */ + String conditionExpression() default ""; /** * Specifies the result type of the mapping method to be used in case multiple mapping methods qualify. @@ -181,13 +428,76 @@ String[] dependsOn() default { }; /** - * In case the source property is {@code null}, the provided default {@link String} value is set. If the designated - * target property is not of type {@code String}, the value will be converted by applying a matching conversion - * method or built-in conversion. + * In case the source property is {@code null}, the provided default {@link String} value is set. *

- * This attribute can not be used together with {@link #constant()} or {@link #expression()}. + * When the designated target property is of type: + *

+ *
    + *
  1. primitive or boxed (e.g. {@code java.lang.Long}). + *

    + * MapStruct checks whether the primitive can be assigned as valid literal to the primitive or boxed type. + *

    + *
      + *
    • + * If possible, MapStruct assigns as literal. + *
    • + *
    • + * If not possible, MapStruct will try to apply a user defined mapping method. + *
    • + *
    + *
  2. + *
  3. 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. + *

  4. + *
+ *

+ * This attribute can not be used together with {@link #constant()}, {@link #expression()} + * or {@link #defaultExpression()}. * * @return Default value to set in case the source property is {@code null}. */ String defaultValue() default ""; + + /** + * Determines when to include a null check on the source property value of a bean mapping. + * + * Can be overridden by the one on {@link MapperConfig}, {@link Mapper} or {@link BeanMapping}. + * + * @since 1.3 + * + * @return strategy how to do null checking + */ + NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + + /** + * The strategy to be applied when the source property is {@code null} or not present. If no strategy is configured, + * the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()}, + * {@link BeanMapping#nullValuePropertyMappingStrategy()} or + * {@link Mapper#nullValuePropertyMappingStrategy()} will be applied. + * + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @since 1.3 + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() + default NullValuePropertyMappingStrategy.SET_TO_NULL; + + /** + * Allows detailed control over the mapping process. + * + * @return the mapping control + * + * @since 1.4 + * + * @see org.mapstruct.control.DeepClone + * @see org.mapstruct.control.NoComplexMapping + * @see org.mapstruct.control.MappingControl + */ + Class mappingControl() default MappingControl.class; + } diff --git a/core/src/main/java/org/mapstruct/MappingConstants.java b/core/src/main/java/org/mapstruct/MappingConstants.java new file mode 100644 index 0000000000..3d3d8a4c77 --- /dev/null +++ b/core/src/main/java/org/mapstruct/MappingConstants.java @@ -0,0 +1,154 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Contains all constants defined in the mapping process. + * + * @author Sjaak Derksen + */ +public final class MappingConstants { + + private MappingConstants() { + } + + /** + * In an {@link ValueMapping} this represents a {@code null} source or target. + */ + public static final String NULL = ""; + + /** + * In an {@link ValueMapping} this represents any source that is not already mapped by either a defined mapping or + * by means of name based mapping. + * + * NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}. + */ + public static final String ANY_REMAINING = ""; + + /** + * In an {@link ValueMapping} this represents any source that is not already mapped by a defined mapping. + * + * NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}. + * + */ + public static final String ANY_UNMAPPED = ""; + + /** + * In an {@link ValueMapping} this represents any target that will be mapped to an + * {@link java.lang.IllegalArgumentException} which will be thrown at runtime. + *

+ * NOTE: The value is only applicable to {@link ValueMapping#target()} and not to {@link ValueMapping#source()}. + */ + public static final String THROW_EXCEPTION = ""; + + /** + * In an {@link EnumMapping} this represent the enum transformation strategy that adds a suffix to the source enum. + * + * @since 1.4 + */ + public static final String SUFFIX_TRANSFORMATION = "suffix"; + + /** + * In an {@link EnumMapping} this represent the enum transformation strategy that strips a suffix from the source + * enum. + * + * @since 1.4 + */ + public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix"; + + /** + * In an {@link EnumMapping} this represent the enum transformation strategy that adds a prefix to the source enum. + * + * @since 1.4 + */ + public static final String PREFIX_TRANSFORMATION = "prefix"; + + /** + * In an {@link EnumMapping} this represent the enum transformation strategy that strips a prefix from the source + * enum. + * + * @since 1.4 + */ + public static final String STRIP_PREFIX_TRANSFORMATION = "stripPrefix"; + + /** + * In an {@link EnumMapping} this represent the enum transformation strategy that applies case transformation + * at the source. + * + * @since 1.5 + */ + public static final String CASE_TRANSFORMATION = "case"; + + /** + * Specifies the component model constants to which the generated mapper should adhere. + * It can be used with the annotation {@link Mapper#componentModel()} or {@link MapperConfig#componentModel()} + * + *

+ * Example: + *

+ *

+    * // Spring component model
+    * @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
+    * 
+ * + * @since 1.5.0 + */ + public static final class ComponentModel { + + private ComponentModel() { + } + + /** + * The mapper uses no component model, instances are typically retrieved + * via {@link org.mapstruct.factory.Mappers#getMapper(java.lang.Class)} + * + */ + public static final String DEFAULT = "default"; + + /** + * 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"; + + /** + * The generated mapper is a Spring bean and can be retrieved via @Autowired + * + */ + public static final String SPRING = "spring"; + + /** + * 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/MappingInheritanceStrategy.java b/core/src/main/java/org/mapstruct/MappingInheritanceStrategy.java new file mode 100644 index 0000000000..ff57b45841 --- /dev/null +++ b/core/src/main/java/org/mapstruct/MappingInheritanceStrategy.java @@ -0,0 +1,38 @@ +/* + * 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 inheriting configurations given for methods of prototype mapping methods (declared on mapper config + * classes) to actual mapping methods declared on mappers referring to such config class via {@link Mapper#config()}. + * + * @author Andreas Gudian + */ +public enum MappingInheritanceStrategy { + /** + * Apply the method-level configuration annotations only if the prototype method is explicitly referenced using + * {@link InheritConfiguration}. + */ + EXPLICIT, + + /** + * Inherit the method-level forward configuration annotations automatically if source and target types of the + * prototype method are assignable from the types of a given mapping method. + */ + AUTO_INHERIT_FROM_CONFIG, + + /** + * Inherit the method-level reverse configuration annotations automatically if source and target types of the + * prototype method are assignable from the target and source types of a given mapping method. + */ + AUTO_INHERIT_REVERSE_FROM_CONFIG, + + /** + * Inherit the method-level forward and reverse configuration annotations automatically if source and target types + * of the prototype method are assignable from the types of a given mapping method. + */ + AUTO_INHERIT_ALL_FROM_CONFIG; +} diff --git a/core/src/main/java/org/mapstruct/MappingTarget.java b/core/src/main/java/org/mapstruct/MappingTarget.java new file mode 100644 index 0000000000..3916448368 --- /dev/null +++ b/core/src/main/java/org/mapstruct/MappingTarget.java @@ -0,0 +1,62 @@ +/* + * 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; + +/** + * Declares a parameter of a mapping method to be the target of the mapping. + *

+ * Not more than one parameter can be declared as {@code MappingTarget}. + *

+ * NOTE: The parameter passed as a mapping target must not be {@code null}. + * + *

+ * Example 1: Update exist bean without return value + *

+ *

+ * @Mapper
+ * public interface HumanMapper {
+ *     void updateHuman(HumanDto humanDto, @MappingTarget Human human);
+ * }
+ * 
+ *

+ * // generates
+ * @Override
+ * public void updateHuman(HumanDto humanDto, Human human) {
+ *     human.setName( humanDto.getName() );
+ *     // ...
+ * }
+ * 
+ *

+ * Example 2: Update exist bean and return it + *

+ *

+ * @Mapper
+ * public interface HumanMapper {
+ *     Human updateHuman(HumanDto humanDto, @MappingTarget Human human);
+ * }
+ * 
+ * // generates: + *

+ * @Override
+ * public Human updateHuman(HumanDto humanDto, Human human) {
+ *     // ...
+ *     human.setName( humanDto.getName() );
+ *     return human;
+ * }
+ *
+ * + * + * @author Andreas Gudian + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface MappingTarget { +} diff --git a/core/src/main/java/org/mapstruct/Mappings.java b/core/src/main/java/org/mapstruct/Mappings.java index 4d5e514304..1578a648a9 100644 --- a/core/src/main/java/org/mapstruct/Mappings.java +++ b/core/src/main/java/org/mapstruct/Mappings.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct; @@ -25,11 +12,37 @@ /** * Configures the mappings of several bean attributes. + *

+ * TIP: When using Java 8 or later, you can omit the @Mappings + * wrapper annotation and directly specify several @Mapping annotations on one method. + * + *

These two examples are equal. + *

+ *

+ * // before Java 8
+ * @Mapper
+ * public interface MyMapper {
+ *     @Mappings({
+ *         @Mapping(target = "firstProperty", source = "first"),
+ *         @Mapping(target = "secondProperty", source = "second")
+ *     })
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ *

+ * // Java 8 and later
+ * @Mapper
+ * public interface MyMapper {
+ *     @Mapping(target = "firstProperty", source = "first"),
+ *     @Mapping(target = "secondProperty", source = "second")
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
* * @author Gunnar Morling */ @Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Mappings { /** diff --git a/core/src/main/java/org/mapstruct/Named.java b/core/src/main/java/org/mapstruct/Named.java new file mode 100644 index 0000000000..773886a7b1 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Named.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * Marks mapping methods with the given qualifier name. Can be used to qualify a single method or all methods of a given + * type by specifying this annotation on the type level. + *

+ * Will be used to select the correct mapping methods when mapping a bean property type, element of an iterable type + * or the key/value of a map type. + *

+ * Example (both methods of {@code Titles} are capable to convert a string, but the ambiguity is resolved by applying + * the qualifiers in {@code @Mapping}: + * + *

+ * 
+ * @Named("TitleTranslator")
+ * public class Titles {
+ *
+ *   @Named("EnglishToGerman")
+ *   public String translateTitleEG(String title) {
+ *       // some mapping logic
+ *   }
+ *
+ *   @Named("GermanToEnglish")
+ *   public String translateTitleGE(String title) {
+ *       // some mapping logic
+ *   }
+ * }
+ *
+ * @Mapper( uses = Titles.class )
+ * public interface MovieMapper {
+ *
+ *    @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
+ *    GermanRelease toGerman( OriginalRelease movies );
+ *
+ * }
+ * 
+ * 
+ * + * The following implementation of {@code MovieMapper} will be generated: + * + *
+ * 
+ *
+ * public class MovieMapperImpl implements MovieMapper {
+ *     private final Titles titles = new Titles();
+ *
+ *     @Override
+ *     public GermanRelease toGerman(OriginalRelease movies) {
+ *         if ( movies == null ) {
+ *             return null;
+ *         }
+ *
+ *         GermanRelease germanRelease = new GermanRelease();
+ *
+ *         germanRelease.setTitle( titles.translateTitleEG( movies.getTitle() ) );
+ *
+ *         return germanRelease;
+ *     }
+ * }
+ * 
+ * 
+ * + * @author Sjaak Derksen + * @see org.mapstruct.Mapping#qualifiedByName() + * @see IterableMapping#qualifiedByName() + * @see MapMapping#keyQualifiedByName() + * @see MapMapping#valueQualifiedByName() + */ +@Target( { ElementType.TYPE, ElementType.METHOD } ) +@Retention( RetentionPolicy.CLASS ) +@Qualifier +public @interface Named { + + /** + * A name qualifying the annotated element + * + * @return the name. + */ + String value(); +} 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 new file mode 100644 index 0000000000..22dba7c58b --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java @@ -0,0 +1,43 @@ +/* + * 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 null source values. + * + * 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 + * as target accessor see {@link CollectionMappingStrategy}, MapStruct will always generate a source property null + * check, regardless the value of the {@link NullValueCheckStrategy} to avoid addition of {@code null} to the target + * collection or map. + * + * @author Sean Huang + */ +public enum NullValueCheckStrategy { + + /** + * This option includes a null check. When: + *
+ *
+ *

    + *
  1. a source value is directly assigned to a target
  2. + *
  3. a source value assigned to a target by calling a type conversion on the target first
  4. + *
+ *
+ * NOTE: mapping methods (generated or hand written) are excluded from this null check. They are intended to + * handle a null source value as 'valid' input. + * + */ + ON_IMPLICIT_CONVERSION, + + /** + * This option always includes a null check. + */ + ALWAYS; + +} diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java new file mode 100644 index 0000000000..4cece83031 --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -0,0 +1,36 @@ +/* + * 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 {@code null} values passed to mapping methods. + * + * @author Sjaak Derksen + */ +public enum NullValueMappingStrategy { + + /** + * 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, + + /** + * If {@code null} is passed to a mapping method, a default value will be returned. The value depends on the kind of + * the annotated method: + *
    + *
  • For bean mapping methods the target type will be instantiated and returned. Any properties of the target type + * which are mapped via {@link Mapping#expression()} or {@link Mapping#constant()} will be populated based on the + * given expression or constant. Note that expressions must be prepared to deal with {@code null} values in this + * 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 new file mode 100644 index 0000000000..0ab30d5271 --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.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; + +/** + * Strategy for dealing with {@code null} or not present properties in the source bean. The + * {@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 + * override {@link Mapper} + * + * The enum only applies to update methods: methods that update a pre-existing target (annotated with + * {@code @}{@link MappingTarget}). + * + *

+ * Note: some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder + * as target accessor see {@link CollectionMappingStrategy}, MapStruct will always generate a source property + * null check, regardless the value of the {@link NullValuePropertyMappingStrategy} to avoid addition of {@code null} + * to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied. + * + * @author Sjaak Derksen + * @since 1.3 + */ +public enum NullValuePropertyMappingStrategy { + + /** + * 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, + + /** + * If a source bean property equals {@code null} the target bean property will be set to its default value. + *

+ * This means: + *

    + *
  1. For {@code Optional} MapStruct generates an {@code Optional.empty()}
  2. + *
  3. For {@code List} MapStruct generates an {@code ArrayList}
  4. + *
  5. For {@code Map} a {@code HashMap}
  6. + *
  7. For arrays an empty array
  8. + *
  9. For {@code String} {@code ""}
  10. + *
  11. for primitive / boxed types a representation of {@code 0} or {@code false}
  12. + *
  13. For all other objects an new instance is created, requiring an empty constructor.
  14. + *
+ *

+ * Make sure that a {@link Mapping#defaultValue()} is defined if no empty constructor is available on + * the default value. + */ + SET_TO_DEFAULT, + + /** + * If a source bean property equals {@code null} the target bean property will be ignored and retain its + * existing value. + */ + 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/ObjectFactory.java b/core/src/main/java/org/mapstruct/ObjectFactory.java new file mode 100644 index 0000000000..dc0e92de8d --- /dev/null +++ b/core/src/main/java/org/mapstruct/ObjectFactory.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; + +/** + * This annotation marks a method as a factory method to create beans. + *

+ * By default beans are created during the mapping process with the default constructor. If a factory method with a + * return type that is assignable to the required object type is present, then the factory method is used instead. + *

+ * Factory methods can be defined without parameters, with an {@code @}{@link TargetType} parameter, a + * {@code @}{@link Context} parameter, or with the mapping source parameter. If any of those parameters are defined, + * then the mapping method that is supposed to use the factory method needs to be declared with an assignable result + * type, assignable context parameter, and/or assignable source types. + *

+ * Note: the usage of this annotation is optional when used in the {@link Mapper#uses()} + * if no source parameters are part of the signature, i.e. it is declared without parameters or only with + * {@code @}{@link TargetType} and/or {@code @}{@link Context}. It is however mandatory when used inside + * an {@code @}{@link Context} annotated class. + *

+ * Example: Using a factory method for entities to check whether the entity already exists in the + * EntityManager and then returns the managed instance: + * + *

+ * 
+ * @ApplicationScoped // CDI component model
+ * public class ReferenceMapper {
+ *
+ *     @PersistenceContext
+ *     private EntityManager em;
+ *
+ *     @ObjectFactory
+ *     public <T extends AbstractEntity> T resolve(AbstractDto sourceDto, @TargetType Class<T> type) {
+ *         T entity = em.find( type, sourceDto.getId() );
+ *         return entity != null ? entity : type.newInstance();
+ *     }
+ * }
+ * 
+ * 
+ *

+ * If there are two factory methods, both serving the same type, one with no parameters and one taking sources as input, + * then the one with the source parameters is favored. If there are multiple such factories, an ambiguity error is + * shown. + * + * @author Remo Meier + * @since 1.2 + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface ObjectFactory { + +} diff --git a/core/src/main/java/org/mapstruct/Qualifier.java b/core/src/main/java/org/mapstruct/Qualifier.java new file mode 100644 index 0000000000..9e18c3e420 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Qualifier.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * Declares an annotation type to be a qualifier. Qualifier annotations allow unambiguously identify a suitable mapping + * method in case several methods qualify to map a bean property, iterable element etc. + *

+ * Can be used in: + *

    + *
  • {@link Mapping#qualifiedBy() }
  • + *
  • {@link BeanMapping#qualifiedBy() }
  • + *
  • {@link IterableMapping#qualifiedBy() }
  • + *
  • {@link MapMapping#keyQualifiedBy() }
  • + *
  • {@link MapMapping#valueQualifiedBy() }
  • + *
  • {@link SubclassMapping#qualifiedBy() }
  • + *
+ *

Example:

+ *

+ * // create qualifiers
+ * @Qualifier
+ * @Target(ElementType.TYPE)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface TitleTranslator {}
+ *
+ * @Qualifier
+ * @Target(ElementType.METHOD)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface EnglishToGerman {}
+ *
+ * @Qualifier
+ * @Target(ElementType.METHOD)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface GermanToEnglish {}
+ * 
+ *

+ * // we can create class with map methods
+ * @TitleTranslator
+ * public class Titles {
+ *     @EnglishToGerman
+ *     public String translateTitleEnglishToGerman(String title) {
+ *         // some mapping logic
+ *     }
+ *     @GermanToEnglish
+ *     public String translateTitleGermanToEnglish(String title) {
+ *         // some mapping logic
+ *     }
+ * }
+ * 
+ *

+ * // usage
+ * @Mapper( uses = Titles.class )
+ * public interface MovieMapper {
+ *      @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
+ *      GermanRelease toGerman( OriginalRelease movies );
+ * }
+ * 
+ *

+ * // generates
+ * public class MovieMapperImpl implements MovieMapper {
+ *      private final Titles titles = new Titles();
+ *      @Override
+ *      public GermanRelease toGerman(OriginalRelease movies) {
+ *          if ( movies == null ) {
+ *              return null;
+ *          }
+ *          GermanRelease germanRelease = new GermanRelease();
+ *          germanRelease.setTitle( titles.translateTitleEnglishToGerman( movies.getTitle() ) );
+ *          return germanRelease;
+ *     }
+ * }
+ * 
+ * + * NOTE: Qualifiers should have {@link RetentionPolicy#CLASS}. + * + * @author Sjaak Derksen + * @see Named + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface Qualifier { +} diff --git a/core/src/main/java/org/mapstruct/ReportingPolicy.java b/core/src/main/java/org/mapstruct/ReportingPolicy.java new file mode 100644 index 0000000000..78b510aac7 --- /dev/null +++ b/core/src/main/java/org/mapstruct/ReportingPolicy.java @@ -0,0 +1,33 @@ +/* + * 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 javax.tools.Diagnostic.Kind; + +/** + * Policy for reporting issues occurring during the generation of a mapper + * implementation. + * + * @author Gunnar Morling + */ +public enum ReportingPolicy { + + /** + * No report will be created for the given issue. + */ + IGNORE, + + /** + * A report with {@link Kind#WARNING} will be created for the given issue. + */ + WARN, + + /** + * A report with {@link Kind#ERROR} will be created for the given issue, + * causing the compilation to fail. + */ + ERROR; +} 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. + * + *

+ * public class PresenceCheckUtils {
+ *
+ *   @SourceParameterCondition
+ *   public static boolean isDefined(Car car) {
+ *      return car != null && car.getId() != null;
+ *   }
+ * }
+ *
+ * @Mapper(uses = PresenceCheckUtils.class)
+ * public interface CarMapper {
+ *
+ *     CarDto map(Car car);
+ * }
+ * 
+ * + * The following implementation of {@code CarMapper} will be generated: + * + *

+ * public class CarMapperImpl implements CarMapper {
+ *
+ *     @Override
+ *     public CarDto map(Car car) {
+ *         if ( !PresenceCheckUtils.isDefined( car ) ) {
+ *             return null;
+ *         }
+ *
+ *         CarDto carDto = new CarDto();
+ *
+ *         carDto.setId( car.getId() );
+ *         // ...
+ *
+ *         return carDto;
+ *     }
+ * }
+ * 
+ * + * @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. + *

+ * + *

+ * @Mapper
+ * public interface MyMapper {
+ *    @SubclassMapping (target = TargetSubclass.class, source = SourceSubclass.class)
+ *    TargetParent mapParent(SourceParent parent);
+ *
+ *    TargetSubclass mapSubclass(SourceSubclass subInstant);
+ * }
+ * 
+ * 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 ) ) + *

+ * // generates
+ * @Override
+ * public TargetParent mapParent(SourceParent parent) {
+ *     TargetParent targetParent1;
+ *     if (parent instanceof SourceSubclass) {
+ *         targetParent1 = mapSubclass( (SourceSubclass) parent );
+ *     }
+ *     else {
+ *         targetParent1 = new TargetParent();
+ *         // ...
+ *     }
+ * }
+ * 
+ * + * @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[] 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. + *

+ *

These two examples are equal. + *

+ *

+ * // before java 8
+ * @Mapper
+ * public interface MyMapper {
+ *     @SubclassMappings({
+ *         @SubclassMapping(source = FirstSub.class, target = FirstTargetSub.class),
+ *         @SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
+ *     })
+ *     ParentTarget toParentTarget(Parent parent);
+ * }
+ * 
+ *

+ * // java 8 and later
+ * @Mapper
+ * public interface MyMapper {
+ *     @SubclassMapping(source = First.class, target = FirstTargetSub.class),
+ *     @SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
+ *     ParentTarget toParentTarget(Parent parent);
+ * }
+ * 
+ * + * @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/TargetType.java b/core/src/main/java/org/mapstruct/TargetType.java new file mode 100644 index 0000000000..9d617fec0c --- /dev/null +++ b/core/src/main/java/org/mapstruct/TargetType.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * Declares a parameter of a custom mapping method to be populated with the target type of the mapping. + *

+ * Not more than one parameter can be declared as {@code TargetType} and that parameter needs to be of type + * {@link Class} (may be parameterized), or a super-type of it. + * + *

+ * Example: + *

+ *

+ * public class EntityFactory {
+ *    public <T extends BaseEntity> T createEntity(@TargetType Class<T> entityClass) {
+ *         return // ... custom factory logic
+ *    }
+ * }
+ * @Mapper(uses = EntityFactory.class)
+ * public interface CarMapper {
+ *     CarEntity carDtoToCar(CarDto dto);
+ * }
+ * 
+ *

+ * // generates
+ * public class CarMapperImpl implements CarMapper {
+ *     private final EntityFactory entityFactory = new EntityFactory();
+ *     @Override
+ *     public CarEntity carDtoToCar(CarDto dto) {
+ *         if ( dto == null ) {
+ *             return null;
+ *         }
+ *         CarEntity carEntity = entityFactory.createEntity( CarEntity.class );
+ *         return carEntity;
+ *     }
+ * }
+ * 
+ * + * @author Andreas Gudian + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface TargetType { +} diff --git a/core/src/main/java/org/mapstruct/ValueMapping.java b/core/src/main/java/org/mapstruct/ValueMapping.java index 2aa4f8df89..7ad3726e48 100644 --- a/core/src/main/java/org/mapstruct/ValueMapping.java +++ b/core/src/main/java/org/mapstruct/ValueMapping.java @@ -1,24 +1,12 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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; @@ -30,20 +18,16 @@ *
    *
  1. Enumeration to Enumeration
  2. *
- *

- * Example 1: + * Example 1: * - *

- * 
- * public enum OrderType { RETAIL, B2B, EXTRA, STANDARD, NORMAL }
+ * 

+ * public enum OrderType { RETAIL, B2B, C2C, EXTRA, STANDARD, NORMAL }
  *
  * public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT }
  *
- * @ValueMappings({
- *    @ValueMapping(source = "EXTRA", target = "SPECIAL"),
- *    @ValueMapping(source = "STANDARD", target = "DEFAULT"),
- *    @ValueMapping(source = "NORMAL", target = "DEFAULT")
- * })
+ * @ValueMapping(target = "SPECIAL", source = "EXTRA"),
+ * @ValueMapping(target = "DEFAULT", source = "STANDARD"),
+ * @ValueMapping(target = "DEFAULT", source = "NORMAL")
  * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
  * 
  * Mapping result:
@@ -59,18 +43,12 @@
  * +---------------------+----------------------------+
  * 
* - * MapStruct will WARN on incomplete mappings. However, if for some reason no match is found an - * {@link java.lang.IllegalStateException} will be thrown. - *

- * Example 2: + * Example 2: * - *

- * 
- * @ValueMappings({
- *    @ValueMapping( source = "<NULL>", target = "DEFAULT" ),
- *    @ValueMapping( source = "STANDARD", target = "<NULL>" ),
- *    @ValueMapping( source = "<ANY_REMAINING>", target = "SPECIAL" )
- * })
+ * 

+ * @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
+ * @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
+ * @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
  * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
  * 
  * Mapping result:
@@ -86,12 +64,28 @@
  * +---------------------+----------------------------+
  * 
* + * Example 3: + * + * MapStruct will WARN on incomplete mappings. However, if for some reason no match is found, an + * {@link java.lang.IllegalStateException} will be thrown. This compile-time error can be avoided by + * using {@link MappingConstants#THROW_EXCEPTION} for {@link ValueMapping#target()}. It will result an + * {@link java.lang.IllegalArgumentException} at runtime. + *

+ * @ValueMapping( source = "STANDARD", target = "DEFAULT" ),
+ * @ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
+ * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+ * 
+ * Mapping result:
+ * {@link java.lang.IllegalArgumentException} with the error message:
+ * Unexpected enum constant: C2C
+ * 
+ * * @author Sjaak Derksen */ +@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. * @@ -120,6 +114,7 @@ *
    *
  1. enum constant name
  2. *
  3. {@link MappingConstants#NULL}
  4. + *
  5. {@link MappingConstants#THROW_EXCEPTION}
  6. *
* * @return The target value. diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index 24fcbf55de..95d4c7d5ce 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct; @@ -25,13 +12,43 @@ /** * Constructs a set of value (constant) mappings. + *

+ * TIP: When using Java 8 or later, you can omit the @ValueMappings + * wrapper annotation and directly specify several @ValueMapping annotations on one method. + * + *

These two examples are equal

+ *

+ * // before Java 8
+ * @Mapper
+ * public interface GenderMapper {
+ *     @ValueMappings({
+ *         @ValueMapping(target = "M", source = "MALE"),
+ *         @ValueMapping(target = "F", source = "FEMALE")
+ *     })
+ *     GenderDto mapToDto(Gender gender);
+ * }
+ * 
+ *

+ * //Java 8 and later
+ * @Mapper
+ * public interface GenderMapper {
+ *     @ValueMapping(target = "M", source = "MALE"),
+ *     @ValueMapping(target = "F", source = "FEMALE")
+ *     GenderDto mapToDto(Gender gender);
+ * }
+ * 
* * @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/DeepClone.java b/core/src/main/java/org/mapstruct/control/DeepClone.java new file mode 100644 index 0000000000..a5264c861e --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/DeepClone.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.control; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.util.Experimental; + +/** + * Clones a source type to a target type (assuming source and target are of the same type). + * + * @author Sjaak Derksen + * + * @since 1.4 + */ +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +public @interface DeepClone { +} diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java new file mode 100644 index 0000000000..12c95c4494 --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/MappingControl.java @@ -0,0 +1,159 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.control; + +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; + +/** + * Controls which means of mapping are considered between the source and the target in mappings. + * + *

+ * There are several applications of MappingControl conceivable. One application, "deep cloning" is + * explained below in the example. + *

+ * + *

+ * Another application is controlling so called "complex mappings", which are not always desirable and sometimes lead to + * unexpected behaviour and prolonged compilation time. + *

+ * + *

Example:Cloning of an object

+ *

+ * When all methods are allowed, MapStruct would make a shallow copy. It would take the ShelveDTO in + * the FridgeDTO and directly enter that as target on the target FridgeDTO. By disabling all + * other kinds of mappings apart from {@link MappingControl.Use#MAPPING_METHOD}, see {@link DeepClone} MapStruct is + * forced to generate mapping methods all through the object graph `FridgeDTO` and hence create a deep clone. + *

+ *

+ * public class FridgeDTO {
+ *
+ *     private ShelveDTO shelve;
+ *
+ *     public ShelveDTO getShelve() {
+ *         return shelve;
+ *     }
+ *
+ *     public void setShelve(ShelveDTO shelve) {
+ *         this.shelve = shelve;
+ *     }
+ * }
+ * 
+ *

+ * public class ShelveDTO {
+ *
+ *     private CoolBeerDTO coolBeer;
+ *
+ *     public CoolBeerDTO getCoolBeer() {
+ *         return coolBeer;
+ *     }
+ *
+ *     public void setCoolBeer(CoolBeerDTO coolBeer) {
+ *         this.coolBeer = coolBeer;
+ *     }
+ * }
+ * 
+ *

+ * public class CoolBeerDTO {
+ *
+ *     private String beerCount;
+ *
+ *     public String getBeerCount() {
+ *         return beerCount;
+ *     }
+ *
+ *     public void setBeerCount(String beerCount) {
+ *         this.beerCount = beerCount;
+ *     }
+ * }
+ * 
+ * + *

+ * @Mapper(mappingControl = DeepClone.class)
+ * public interface CloningMapper {
+ *
+ *     CloningMapper INSTANCE = Mappers.getMapper( CloningMapper.class );
+ *
+ *     FridgeDTO clone(FridgeDTO in);
+ *
+ * }
+ * 
+ * + * @author Sjaak Derksen + * + * @since 1.4 + */ +@Retention(RetentionPolicy.CLASS) +@Repeatable(MappingControls.class) +@Target( ElementType.ANNOTATION_TYPE ) +@MappingControl( MappingControl.Use.DIRECT ) +@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION ) +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +@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 { + + /** + * Controls the mapping, allows for type conversion from source type to target type + *

+ * Type conversions are typically supported directly in Java. The "toString()" is such an example, + * which allows for mapping for instance a {@link java.lang.Number} type to a {@link java.lang.String}. + *

+ * Please refer to the MapStruct guide for more info. + * + * @since 1.4 + */ + BUILT_IN_CONVERSION, + + /** + * Controls the mapping from source to target type, allows mapping by calling: + *

    + *
  1. A type conversion, passed into a mapping method
  2. + *
  3. A mapping method, passed into a type conversion
  4. + *
  5. A mapping method passed into another mapping method
  6. + *
+ * + * @since 1.4 + */ + COMPLEX_MAPPING, + /** + * Controls the mapping, allows for a direct mapping from source type to target type. + *

+ * This means if source type and target type are of the same type, MapStruct will not perform + * any mappings anymore and assign the target to the source direct. + *

+ * An exception are types from the package {@code java}, which will be mapped always directly. + * + * @since 1.4 + */ + DIRECT, + + /** + * Controls the mapping, allows for Direct Mapping from source type to target type. + *

+ * The mapping method can be either a custom referred mapping method, or a MapStruct built in + * mapping method. + * + * @since 1.4 + */ + MAPPING_METHOD + } + +} diff --git a/core/src/main/java/org/mapstruct/control/MappingControls.java b/core/src/main/java/org/mapstruct/control/MappingControls.java new file mode 100644 index 0000000000..c1663e551e --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/MappingControls.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.control; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows multiple {@link MappingControl} on a class declaration. + * + * @author Sjaak Derksen + * + * @since 1.4 + */ +@Retention(RetentionPolicy.CLASS) +@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/control/NoComplexMapping.java b/core/src/main/java/org/mapstruct/control/NoComplexMapping.java new file mode 100644 index 0000000000..5894f602a5 --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/NoComplexMapping.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.control; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.util.Experimental; + +/** + * Disables complex mappings, mappings that require 2 mapping means (method, built-in conversion) to constitute + * a mapping from source to target. + * + * @see MappingControl.Use#COMPLEX_MAPPING + * + * @author Sjaak Derksen + * + * @since 1.4 + */ +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.DIRECT ) +@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION ) +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +public @interface NoComplexMapping { +} diff --git a/core/src/main/java/org/mapstruct/factory/Mappers.java b/core/src/main/java/org/mapstruct/factory/Mappers.java new file mode 100644 index 0000000000..d20bd59fa3 --- /dev/null +++ b/core/src/main/java/org/mapstruct/factory/Mappers.java @@ -0,0 +1,168 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.factory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +import org.mapstruct.Mapper; + +/** + * Factory for obtaining mapper instances if no explicit component model such as CDI is configured via + * {@link Mapper#componentModel()}. + *

+ * Mapper implementation types are expected to have the same fully qualified name as their interface type, with the + * suffix {@code Impl} appended. When using this factory, mapper types - and any mappers they use - are instantiated by + * invoking their public no-args constructor. + *

+ * By convention, a single instance of each mapper is retrieved from the factory and exposed on the mapper interface + * type by declaring a member named {@code INSTANCE} like this: + * + *

+ * @Mapper
+ * public interface CustomerMapper {
+ *
+ *     CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
+ *
+ *     // mapping methods...
+ * }
+ * 
+ * + * @author Gunnar Morling + */ +public class Mappers { + + private static final String IMPLEMENTATION_SUFFIX = "Impl"; + + private Mappers() { + } + + /** + * Returns an instance of the given mapper type. + * + * @param clazz The type of the mapper to return. + * @param The type of the mapper to create. + * + * @return An instance of the given mapper type. + */ + public static T getMapper(Class clazz) { + try { + List classLoaders = collectClassLoaders( clazz.getClassLoader() ); + + return getMapper( clazz, classLoaders ); + } + catch ( ClassNotFoundException | NoSuchMethodException e ) { + throw new RuntimeException( e ); + } + } + + private static T getMapper(Class mapperType, Iterable classLoaders) + throws ClassNotFoundException, NoSuchMethodException { + + for ( ClassLoader classLoader : classLoaders ) { + T mapper = doGetMapper( mapperType, classLoader ); + if ( mapper != null ) { + return mapper; + } + } + + throw new ClassNotFoundException("Cannot find implementation for " + mapperType.getName() ); + } + + private static T doGetMapper(Class clazz, ClassLoader classLoader) throws NoSuchMethodException { + try { + @SuppressWarnings( "unchecked" ) + Class implementation = (Class) classLoader.loadClass( clazz.getName() + IMPLEMENTATION_SUFFIX ); + Constructor constructor = implementation.getDeclaredConstructor(); + constructor.setAccessible( true ); + + return constructor.newInstance(); + } + catch ( ClassNotFoundException e ) { + return getMapperFromServiceLoader( clazz, classLoader ); + } + catch ( InstantiationException | InvocationTargetException | IllegalAccessException e ) { + throw new RuntimeException( e ); + } + } + + /** + * Returns the class of the implementation for the given mapper type. + * + * @param clazz The type of the mapper to return. + * @param The type of the mapper to create. + * + * @return A class of the implementation for the given mapper type. + * + * @since 1.3 + */ + public static Class getMapperClass(Class clazz) { + try { + List classLoaders = collectClassLoaders( clazz.getClassLoader() ); + + return getMapperClass( clazz, classLoaders ); + } + catch ( ClassNotFoundException e ) { + throw new RuntimeException( e ); + } + } + + private static Class getMapperClass(Class mapperType, Iterable classLoaders) + throws ClassNotFoundException { + + for ( ClassLoader classLoader : classLoaders ) { + Class mapperClass = doGetMapperClass( mapperType, classLoader ); + if ( mapperClass != null ) { + return mapperClass; + } + } + + throw new ClassNotFoundException( "Cannot find implementation for " + mapperType.getName() ); + } + + @SuppressWarnings("unchecked") + private static Class doGetMapperClass(Class clazz, ClassLoader classLoader) { + try { + return (Class) classLoader.loadClass( clazz.getName() + IMPLEMENTATION_SUFFIX ); + } + catch ( ClassNotFoundException e ) { + T mapper = getMapperFromServiceLoader( clazz, classLoader ); + if ( mapper != null ) { + return (Class) mapper.getClass(); + } + + return null; + } + } + + private static T getMapperFromServiceLoader(Class clazz, ClassLoader classLoader) { + ServiceLoader loader = ServiceLoader.load( clazz, classLoader ); + + for ( T mapper : loader ) { + if ( mapper != null ) { + return mapper; + } + } + + return null; + } + + private static List collectClassLoaders(ClassLoader classLoader) { + List classLoaders = new ArrayList<>( 3 ); + classLoaders.add( classLoader ); + + if ( Thread.currentThread().getContextClassLoader() != null ) { + classLoaders.add( Thread.currentThread().getContextClassLoader() ); + } + + classLoaders.add( Mappers.class.getClassLoader() ); + + return classLoaders; + } +} diff --git a/core/src/main/java/org/mapstruct/factory/package-info.java b/core/src/main/java/org/mapstruct/factory/package-info.java new file mode 100644 index 0000000000..532857a401 --- /dev/null +++ b/core/src/main/java/org/mapstruct/factory/package-info.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 + */ +/** + *

+ * Provides the {@link org.mapstruct.factory.Mappers} factory class for the retrieval of mapper instances if no + * explicit component model such as CDI is configured via {@link org.mapstruct.Mapper#componentModel()}. + *

+ * + */ +package org.mapstruct.factory; diff --git a/core/src/main/java/org/mapstruct/package-info.java b/core/src/main/java/org/mapstruct/package-info.java new file mode 100644 index 0000000000..5a53b5a6d3 --- /dev/null +++ b/core/src/main/java/org/mapstruct/package-info.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 + */ +/** + *

+ * MapStruct is a code generator which simplifies the implementation of mappings between Java bean types by generating + * mapping code at compile time, following a convention-over-configuration approach. The generated code uses plain + * method invocations and thus is fast and type-safe. + *

+ *

+ * This package contains several annotations which allow to configure how mapper interfaces are generated. + *

+ * + * @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 new file mode 100644 index 0000000000..5fd5eb3b5c --- /dev/null +++ b/core/src/main/java/org/mapstruct/util/Experimental.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.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Features that are marked with this annotation are considered experimental. + * + * @author Andreas Gudian + */ +@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 new file mode 100644 index 0000000000..cde4b5be3b --- /dev/null +++ b/core/src/test/java/org/mapstruct/factory/MappersTest.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.factory; + +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}. + * + * @author Gunnar Morling + */ +public class MappersTest { + + @Test + public void shouldReturnImplementationInstance() { + + Foo mapper = Mappers.getMapper( Foo.class ); + assertThat( mapper ).isNotNull(); + } + + @Test + public void shouldReturnImplementationClass() { + + Class mapperClass = Mappers.getMapperClass( Foo.class ); + assertThat( mapperClass ).isNotNull(); + assertThat( mapperClass ).isNotExactlyInstanceOf( Foo.class ); + } + + /** + * Checks if an implementation of a nested mapper can be found. This is a special case since + * it is named + */ + @Test + public void findsNestedMapperImpl() { + assertThat( Mappers.getMapper( SomeClass.Foo.class ) ).isNotNull(); + assertThat( Mappers.getMapper( SomeClass.NestedClass.Foo.class ) ).isNotNull(); + } + + @Test + public void findsNestedMapperImplClass() { + assertThat( Mappers.getMapperClass( SomeClass.Foo.class ) ).isNotNull(); + assertThat( Mappers.getMapperClass( SomeClass.NestedClass.Foo.class ) ).isNotNull(); + } + + @Test + public void shouldReturnPackagePrivateImplementationInstance() { + assertThat( Mappers.getMapper( PackagePrivateMapper.class ) ).isNotNull(); + } + + @Test + public void shouldReturnPackagePrivateImplementationClass() { + assertThat( Mappers.getMapperClass( PackagePrivateMapper.class ) ).isNotNull(); + } +} diff --git a/core/src/test/java/org/mapstruct/factory/PackagePrivateMapper.java b/core/src/test/java/org/mapstruct/factory/PackagePrivateMapper.java new file mode 100644 index 0000000000..4fbc97f26d --- /dev/null +++ b/core/src/test/java/org/mapstruct/factory/PackagePrivateMapper.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.factory; + +interface PackagePrivateMapper { + +} diff --git a/core/src/test/java/org/mapstruct/factory/PackagePrivateMapperImpl.java b/core/src/test/java/org/mapstruct/factory/PackagePrivateMapperImpl.java new file mode 100644 index 0000000000..2aca92abef --- /dev/null +++ b/core/src/test/java/org/mapstruct/factory/PackagePrivateMapperImpl.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 + */ +package org.mapstruct.factory; + +class PackagePrivateMapperImpl implements PackagePrivateMapper { +} diff --git a/core/src/test/java/org/mapstruct/test/model/Foo.java b/core/src/test/java/org/mapstruct/test/model/Foo.java new file mode 100644 index 0000000000..4ee788efcf --- /dev/null +++ b/core/src/test/java/org/mapstruct/test/model/Foo.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.test.model; + +public interface Foo { + +} diff --git a/core/src/test/java/org/mapstruct/test/model/FooImpl.java b/core/src/test/java/org/mapstruct/test/model/FooImpl.java new file mode 100644 index 0000000000..0d90dabebc --- /dev/null +++ b/core/src/test/java/org/mapstruct/test/model/FooImpl.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.test.model; + +public class FooImpl implements Foo { + +} diff --git a/core/src/test/java/org/mapstruct/test/model/SomeClass$FooImpl.java b/core/src/test/java/org/mapstruct/test/model/SomeClass$FooImpl.java new file mode 100644 index 0000000000..a9e03641fe --- /dev/null +++ b/core/src/test/java/org/mapstruct/test/model/SomeClass$FooImpl.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.test.model; + +/** + * For testing naming of implementations of nested mappers (issue 611). + * + * @author Tillmann Gaida + */ +/* + * Turn off checkstyle since the underscore introduced by issue 611 violates the class naming + * convention. + */ +// CHECKSTYLE:OFF +public class SomeClass$FooImpl implements SomeClass.Foo { + +} diff --git a/core/src/test/java/org/mapstruct/test/model/SomeClass$NestedClass$FooImpl.java b/core/src/test/java/org/mapstruct/test/model/SomeClass$NestedClass$FooImpl.java new file mode 100644 index 0000000000..53331de181 --- /dev/null +++ b/core/src/test/java/org/mapstruct/test/model/SomeClass$NestedClass$FooImpl.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.test.model; + +/** + * For testing naming of implementations of nested mappers (issue 611). + * + * @author Tillmann Gaida + */ +/* + * Turn off checkstyle since the underscore introduced by issue 611 violates the class naming + * convention. + */ +// CHECKSTYLE:OFF +public class SomeClass$NestedClass$FooImpl implements SomeClass.NestedClass.Foo { + +} diff --git a/core/src/test/java/org/mapstruct/test/model/SomeClass.java b/core/src/test/java/org/mapstruct/test/model/SomeClass.java new file mode 100644 index 0000000000..d25841145c --- /dev/null +++ b/core/src/test/java/org/mapstruct/test/model/SomeClass.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.test.model; + +/** + * The sole purpose of this class is to have a place to nest the Foo interface. + * + * @author Tillmann Gaida + */ +public class SomeClass { + public interface Foo { } + + public static class NestedClass { + public interface Foo { } + } +} diff --git a/distribution/pom.xml b/distribution/pom.xml index a55eaaa72d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -38,10 +25,6 @@ org.mapstruct mapstruct - - org.mapstruct - mapstruct-jdk8 - org.mapstruct mapstruct-processor @@ -51,6 +34,23 @@ org.freemarker freemarker + + + org.mapstruct.tools.gem + gem-api + + + + org.jetbrains.kotlin + kotlin-metadata-jvm + + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -71,7 +71,7 @@ org.freemarker freemarker ${project.build.directory}/freemarker-unpacked - META-INF/LICENSE.txt,META-INF/NOTICE.txt + META-INF/LICENSE @@ -95,18 +95,17 @@ maven-javadoc-plugin - ${basedir}/../core-common/src/main/java; ${basedir}/../core/src/main/java; ${basedir}/../processor/src/main/java - http://docs.oracle.com/javase/7/docs/api/ + https://docs.oracle.com/javase/8/docs/api/ MapStruct Packages MapStruct ${project.version} MapStruct ${project.version} - Gunnar Morling; All rights reserved. Released under the Apache Software License 2.0.]]> + MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]> @@ -114,18 +113,16 @@ MapStruct API org.mapstruct* + + MapStruct Processor SPI + org.mapstruct.ap.spi* + MapStruct Processor org.mapstruct.ap* - org.jboss.apiviz.APIviz - - org.jboss.apiviz - apiviz - 1.3.2.GA - true UTF-8 UTF-8 @@ -134,6 +131,23 @@ true true true + + + + + if (typeof useModuleDirectories !== 'undefined') { + useModuleDirectories = false; + } + + ]]> + + --allow-script-in-comments + @@ -153,7 +167,7 @@ ${basedir}/src/main/assembly/dist.xml mapstruct-${project.version} - gnu + posix @@ -174,4 +188,21 @@ + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + true + + + + diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml index 7a4ae43095..e0bc00496b 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -1,22 +1,9 @@ @@ -32,7 +19,6 @@ lib org.mapstruct:mapstruct - org.mapstruct:mapstruct-jdk8 org.mapstruct:mapstruct-processor @@ -56,11 +42,7 @@ / - target/freemarker-unpacked/META-INF/NOTICE.txt - / - - - target/freemarker-unpacked/META-INF/LICENSE.txt + target/freemarker-unpacked/META-INF/LICENSE etc/freemarker @@ -94,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api diff --git a/documentation/pom.xml b/documentation/pom.xml index 111b34280e..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -34,9 +21,9 @@ MapStruct Documentation - 1.5.0-alpha.11 - 1.5.4 - 1.7.21 + 1.6.0 + 2.5.1 + 9.2.17.0 @@ -46,7 +33,7 @@ org.asciidoctor asciidoctor-maven-plugin - 1.5.3 + 2.1.0 org.asciidoctor @@ -65,7 +52,7 @@ - coderay + mapstruct-reference-guide.asciidoc ${project.version} font diff --git a/documentation/src/main/asciidoc/chapter-1-introduction.asciidoc b/documentation/src/main/asciidoc/chapter-1-introduction.asciidoc new file mode 100644 index 0000000000..1d73a11078 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-1-introduction.asciidoc @@ -0,0 +1,16 @@ +[[introduction]] +== Introduction + +MapStruct is a Java http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html[annotation processor] for the generation of type-safe bean mapping classes. + +All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar. + +Compared to writing mapping code from hand, MapStruct saves time by generating code which is tedious and error-prone to write. Following a convention over configuration approach, MapStruct uses sensible defaults but steps out of your way when it comes to configuring or implementing special behavior. + +Compared to dynamic mapping frameworks, MapStruct offers the following advantages: + +* Fast execution by using plain method invocations instead of reflection +* Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no accidental mapping of an order entity into a customer DTO etc. +* Clear error-reports at build time, if +** mappings are incomplete (not all target properties are mapped) +** mappings are incorrect (cannot find a proper mapping method or type conversion) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc new file mode 100644 index 0000000000..306169193d --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -0,0 +1,667 @@ +== Advanced mapping options +This chapter describes several advanced options which allow to fine-tune the behavior of the generated mapping code as needed. + +[[default-values-and-constants]] +=== Default values and constants + +Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values. When the target type is a primitive or a boxed type, the String value is taken literal. Bit / octal / decimal / hex patterns are allowed in such a case as long as they are a valid literal. +In all other cases, constant or default values are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property. + +A mapping with a constant must not include a reference to a source property. The following example shows some mappings using default values and constants: + +.Mapping method with default values and constants +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(uses = StringListMapper.class) +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined") + @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1") + @Mapping(target = "stringConstant", constant = "Constant Value") + @Mapping(target = "integerConstant", constant = "14") + @Mapping(target = "longWrapperConstant", constant = "3001") + @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014") + @Mapping(target = "stringListConstants", constant = "jack-jill-tom") + Target sourceToTarget(Source s); +} +---- +==== + +If `s.getStringProp() == null`, then the target property `stringProperty` will be set to `"undefined"` instead of applying the value from `s.getStringProp()`. If `s.getLongProperty() == null`, then the target property `longProperty` will be set to `-1`. +The String `"Constant Value"` is set as is to the target property `stringConstant`. The value `"3001"` is type-converted to the `Long` (wrapper) class of target property `longWrapperConstant`. Date properties also require a date format. The constant `"jack-jill-tom"` demonstrates how the hand-written class `StringListMapper` is invoked to map the dash-separated list into a `List`. + +[[expressions]] +=== Expressions + +By means of Expressions it will be possible to include constructs from a number of languages. + +Currently only Java is supported as a language. This feature is e.g. useful to invoke constructors. The entire source object is available for usage in the expression. Care should be taken to insert only valid Java code: MapStruct will not validate the expression at generation-time, but errors will show up in the generated classes during compilation. + +The example below demonstrates how two source properties can be mapped to one target: + +.Mapping method using an expression +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target = "timeAndFormat", + expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )") + Target sourceToTarget(Source s); +} +---- +==== + +The example demonstrates how the source properties `time` and `format` are composed into one target property `TimeAndFormat`. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `TimeAndFormat` class (unless it's used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining `imports` on the `@Mapper` annotation. + +.Declaring an import +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +imports org.sample.TimeAndFormat; + +@Mapper( imports = TimeAndFormat.class ) +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target = "timeAndFormat", + expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )") + Target sourceToTarget(Source s); +} +---- +==== + +[[default-expressions]] +=== Default Expressions + +Default expressions are a combination of default values and expressions. They will only be used when the source attribute is `null`. + +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 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 +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +imports java.util.UUID; + +@Mapper( imports = UUID.class ) +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )") + Target sourceToTarget(Source s); +} +---- +==== + +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 + +When result types have an inheritance relation, selecting either mapping method (`@Mapping`) or a factory method (`@BeanMapping`) can become ambiguous. Suppose an Apple and a Banana, which are both specializations of Fruit. + +.Specifying the result type of a bean mapping method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper( uses = FruitFactory.class ) +public interface FruitMapper { + + @BeanMapping( resultType = Apple.class ) + Fruit map( FruitDto source ); + +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class FruitFactory { + + public Apple createApple() { + return new Apple( "Apple" ); + } + + public Banana createBanana() { + return new Banana( "Banana" ); + } +} +---- +==== + +So, which `Fruit` must be factorized in the mapping method `Fruit map(FruitDto source);`? A `Banana` or an `Apple`? Here's where the `@BeanMapping#resultType` comes in handy. It controls the factory method to select, or in absence of a factory method, the return type to create. + +[TIP] +==== +The same mechanism is present on mapping: `@Mapping#resultType` and works like you expect it would: it selects the mapping method with the desired result type when present. +==== + +[TIP] +==== +The mechanism is also present on iterable mapping and map mapping. `@IterableMapping#elementTargetType` is used to select the mapping method with the desired element in the resulting `Iterable`. For the `@MapMapping` a similar purpose is served by means of `#MapMapping#keyTargetType` and `MapMapping#valueTargetType`. +==== + +[[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, 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: + +* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present. +* *Iterables / Arrays*: an empty iterable will be returned. +* *Maps*: an empty map will be returned. + +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`, or `Optional.empty()` if the target property is `Optional`. + +However: + +1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result can be altered to return *default* values. +For `List` MapStruct generates an `ArrayList`, for `Map` a `LinkedHashMap`, for arrays an empty array, for `String` `""` and for primitive / boxed types a representation of `false` or `0`. +For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`. + +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] +==== +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 of the value of the `NullValuePropertyMappingStrategy`, to avoid addition of `null` to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied. +==== + +[TIP] +==== +`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 + +MapStruct offers control over when to generate a `null` check. By default (`nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION`) a `null` check will be generated for: + +* direct setting of source value to target value when target is primitive and source is not. +* applying type conversion and then: +.. calling the setter on the target. +.. calling another type conversion and subsequently calling the setter on the target. +.. calling a mapping method and subsequently calling the setter on the target. + +First calling a mapping method on the source property is not protected by a null check. Therefore generated mapping methods will do a null check prior to carrying out mapping on a source property. Handwritten mapping methods must take care of null value checking. They have the possibility to add 'meaning' to `null`. For instance: mapping `null` to a default value. + +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 `@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 +Some frameworks generate bean properties that have a source presence checker. Often this is in the form of a method `hasXYZ`, `XYZ` being a property on the source bean in a bean mapping method. MapStruct will call this `hasXYZ` instead of performing a `null` check when it finds such `hasXYZ` method. + +[TIP] +==== +The source presence checker name can be changed in the MapStruct service provider interface (SPI). It can also be deactivated in this way. +==== + +[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 +null check, regardless the value of the `NullValueCheckStrategy` to avoid addition of `null` to the target collection or map. +==== + +[[conditional-mapping]] +=== Conditional Mapping + +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 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 not empty then you can do something like: + +.Mapper using custom condition check method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car); + + @Condition + default boolean isNotEmpty(String value) { + return value != null && !value.isEmpty(); + } +} +---- +==== + +The generated 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) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + if ( isNotEmpty( car.getOwner() ) ) { + carDto.setOwner( car.getOwner() ); + } + + // Mapping of other properties + + return carDto; + } +} +---- +==== + +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. +==== + +[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 + +Calling applications may require handling of exceptions when calling a mapping method. These exceptions could be thrown by hand-written logic and by the generated built-in mapping methods or type-conversions of MapStruct. When the calling application requires handling of exceptions, a throws clause can be defined in the mapping method: + +.Mapper using custom method declaring checked exception +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(uses = HandWritten.class) +public interface CarMapper { + + CarDto carToCarDto(Car car) throws GearException; +} +---- +==== + +The hand written logic might look like this: + +.Custom mapping method declaring checked exception +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class HandWritten { + + private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"}; + + public String toGear(Integer gear) throws GearException, FatalException { + if ( gear == null ) { + throw new FatalException("null is not a valid gear"); + } + + if ( gear < 0 && gear > GEAR.length ) { + throw new GearException("invalid gear"); + } + return GEAR[gear]; + } +} +---- +==== + +MapStruct now, wraps the `FatalException` in a `try-catch` block and rethrows an unchecked `RuntimeException`. MapStruct delegates handling of the `GearException` to the application logic because it is defined as throws clause in the `carToCarDto` method: + +.try-catch block in generated implementation +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public CarDto carToCarDto(Car car) throws GearException { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + try { + carDto.setGear( handWritten.toGear( car.getGear() ) ); + } + catch ( FatalException e ) { + throw new RuntimeException( e ); + } + + return carDto; +} +---- +==== + +Some **notes** on null checks. MapStruct does provide null checking only when required: when applying type-conversions or constructing a new type by invoking its constructor. This means that the user is responsible in hand-written code for returning valid non-null objects. Also null objects can be handed to hand-written code, since MapStruct does not want to make assumptions on the meaning assigned by the user to a null object. Hand-written code has to deal with this. diff --git a/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc new file mode 100644 index 0000000000..efacaedbe5 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc @@ -0,0 +1,167 @@ +== Reusing mapping configurations + +This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types. + +[[mapping-configuration-inheritance]] +=== Mapping configuration inheritance + +Method-level configuration annotations such as `@Mapping`, `@BeanMapping`, `@IterableMapping`, etc., can be *inherited* from one mapping method to a *similar* method using the annotation `@InheritConfiguration`: + +.Update method inheriting its configuration +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(target = "numberOfSeats", source = "seatCount") + Car carDtoToCar(CarDto car); + + @InheritConfiguration + void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); +} +---- +==== + +The example above declares a mapping method `carDtoToCar()` with a configuration to define how the property `numberOfSeats` in the type `Car` shall be mapped. The update method that performs the mapping on an existing instance of `Car` needs the same configuration to successfully map all properties. Declaring `@InheritConfiguration` on the method lets MapStruct search for inheritance candidates to apply the annotations of the method that is inherited from. + +One method *A* can inherit the configuration from another method *B* if all types of *A* (source types and result type) are assignable to the corresponding types of *B*. + +Methods that are considered for inheritance need to be defined in the current mapper, a super class/interface, or in the shared configuration interface (as described in <>). + +In case more than one method is applicable as source for the inheritance, the method name must be specified within the annotation: `@InheritConfiguration( name = "carDtoToCar" )`. + +A method can use `@InheritConfiguration` and override or amend the configuration by additionally applying `@Mapping`, `@BeanMapping`, etc. + +[NOTE] +==== +`@InheritConfiguration` cannot refer to methods in a used mapper. +==== + +[[inverse-mappings]] +=== Inverse mappings + +In case of bi-directional mappings, e.g. from entity to DTO and from DTO to entity, the mapping rules for the forward method and the reverse method are often similar and can simply be inversed by switching `source` and `target`. + +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] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(target = "seatCount", source = "numberOfSeats") + CarDto carToDto(Car car); + + @InheritInverseConfiguration + @Mapping(target = "numberOfSeats", ignore = true) + Car carDtoToCar(CarDto carDto); +} +---- +==== + +Here the `carDtoToCar()` method is the reverse mapping method for `carToDto()`. Note that any attribute mappings from `carToDto()` will be applied to the corresponding reverse mapping method as well. They are automatically reversed and copied to the method with the `@InheritInverseConfiguration` annotation. + +Specific mappings from the inversed method can (optionally) be overridden by `ignore`, `expression` or `constant` in the mapping, e.g. like this: `@Mapping(target = "numberOfSeats", ignore=true)`. + +A method *A* is considered a *reverse* method of a method *B*, if the result type of *A* is the *same* as the single source type of *B* and if the single source type of *A* is the *same* as the result type of *B*. + +Methods that are considered for inverse inheritance need to be defined in the current mapper, a super class/interface. + +If multiple methods qualify, the method from which to inherit the configuration needs to be specified using the `name` property like this: `@InheritInverseConfiguration(name = "carToDto")`. + +`@InheritConfiguration` takes, in case of conflict precedence over `@InheritInverseConfiguration`. + +Configurations are inherited transitively. So if method `C` defines a mapping `@Mapping( target = "x", ignore = true)`, `B` defines a mapping `@Mapping( target = "y", ignore = true)`, then if `A` inherits from `B` inherits from `C`, `A` will inherit mappings for both property `x` and `y`. + +`@Mapping#expression`, `@Mapping#defaultExpression`, `@Mapping#defaultValue` and `@Mapping#constant` are excluded (silently ignored) in `@InheritInverseConfiguration`. + +`@Mapping#ignore` is only applied when `@Mapping#source` is also present in `@InheritInverseConfiguration`. + +Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2 release. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping. + +[NOTE] +==== +`@InheritInverseConfiguration` cannot refer to methods in a used mapper. +==== + +[[shared-configurations]] +=== Shared configurations + +MapStruct offers the possibility to define a shared configuration by pointing to a central interface annotated with `@MapperConfig`. For a mapper to use the shared configuration, the configuration interface needs to be defined in the `@Mapper#config` property. + +The `@MapperConfig` annotation has the same attributes as the `@Mapper` annotation. Any attributes not given via `@Mapper` will be inherited from the shared configuration. Attributes specified in `@Mapper` take precedence over the attributes specified via the referenced configuration class. List properties such as `uses` are simply combined: + +.Mapper configuration class and mapper using it +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@MapperConfig( + uses = CustomMapperViaMapperConfig.class, + unmappedTargetPolicy = ReportingPolicy.ERROR +) +public interface CentralConfig { +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) +// Effective configuration: +// @Mapper( +// uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class }, +// unmappedTargetPolicy = ReportingPolicy.ERROR +// ) +public interface SourceTargetMapper { + ... +} + +---- +==== + +The interface holding the `@MapperConfig` annotation may also declare *prototypes* of mapping methods that can be used to inherit method-level mapping annotations from. Such prototype methods are not meant to be implemented or used as part of the mapper API. + +.Mapper configuration class with prototype methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@MapperConfig( + uses = CustomMapperViaMapperConfig.class, + unmappedTargetPolicy = ReportingPolicy.ERROR, + mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG +) +public interface CentralConfig { + + // Not intended to be generated, but to carry inheritable mapping annotations: + @Mapping(target = "primaryKey", source = "technicalKey") + BaseEntity anyDtoToEntity(BaseDto dto); +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) +public interface SourceTargetMapper { + + @Mapping(target = "numberOfSeats", source = "seatCount") + // additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto: + // @Mapping(target = "primaryKey", source = "technicalKey") + Car toCar(CarDto car) +} +---- +==== + +The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingInheritanceStrategy()` configure when the method-level mapping configuration annotations are inherited from prototype methods in the interface to methods in the mapper: + +* `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`. diff --git a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc new file mode 100644 index 0000000000..dc07b30e62 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc @@ -0,0 +1,271 @@ +== Customizing mappings + +Sometimes it's needed to apply custom logic before or after certain mapping methods. MapStruct provides two ways for doing so: decorators which allow for a type-safe customization of specific mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a generic customization of mapping methods with given source or target types. + +[[customizing-mappers-using-decorators]] +=== Mapping customization with decorators + +In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can't be set by a generated method implementation. MapStruct supports this requirement using decorators. + +[TIP] +When working with the component model `cdi`, use https://docs.jboss.org/cdi/spec/1.0/html/decorators.html[CDI decorators] with MapStruct mappers instead of the `@DecoratedWith` annotation described here. + +To apply a decorator to a mapper class, specify it using the `@DecoratedWith` annotation. + +.Applying a decorator +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); + + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} +---- +==== + +The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class which allows to only implement those methods of the mapper interface which you want to customize. For all non-implemented methods, a simple delegation to the original mapper will be generated using the default generation routine. + +The `PersonMapperDecorator` shown below customizes the `personToPersonDto()`. It sets an additional attribute which is not present in the source type of the mapping. The `addressToAddressDto()` method is not customized. + +.Implementing a decorator +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public abstract class PersonMapperDecorator implements PersonMapper { + + private final PersonMapper delegate; + + public PersonMapperDecorator(PersonMapper delegate) { + this.delegate = delegate; + } + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setFullName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} +---- +==== + +The example shows how you can optionally inject a delegate with the generated default implementation and use this delegate in your customized decorator methods. + +For a mapper with `componentModel = "default"`, define a constructor with a single parameter which accepts the type of the decorated mapper. + +When working with the component models `spring` or `jsr330`, this needs to be handled differently. + +[[decorators-with-spring]] +==== Decorators with the Spring component model + +When using `@DecoratedWith` on a mapper with component model `spring`, the generated implementation of the original mapper is annotated with the Spring annotation `@Qualifier("delegate")`. To autowire that bean in your decorator, add that qualifier annotation as well: + +.Spring-based decorator +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public abstract class PersonMapperDecorator implements PersonMapper { + + @Autowired + @Qualifier("delegate") + private PersonMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } + } +---- +==== + +The generated class that extends the decorator is annotated with Spring's `@Primary` annotation. To autowire the decorated mapper in the application, nothing special needs to be done: + +.Using a decorated mapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Autowired +private PersonMapper personMapper; // injects the decorator, with the injected original mapper +---- +==== + +[[decorators-with-jsr-330]] +==== Decorators with the JSR 330 component model + +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 `@Named("fully-qualified-name-of-generated-implementation")` (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): + +.JSR 330 based decorator +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public abstract class PersonMapperDecorator implements PersonMapper { + + @Inject + @Named("org.examples.PersonMapperImpl_") + private PersonMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } +} +---- +==== + +Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for decorated mappers, the parameterless `@Named` annotation must be added to select the decorator to be injected: + +.Using a decorated mapper with JSR 330 +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Inject +@Named +private PersonMapper personMapper; // injects the decorator, with the injected original mapper +---- +==== + +[[customizing-mappings-with-before-and-after]] +=== Mapping customization with before-mapping and after-mapping methods + +Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can use *callback methods* that are invoked before the mapping starts or after the mapping finished. + +Callback methods can be implemented in the abstract mapper itself, in a type reference in `Mapper#uses`, or in a type used as `@Context` parameter. + +.Mapper with @BeforeMapping and @AfterMapping hooks +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public abstract class VehicleMapper { + + @BeforeMapping + protected void flushEntity(AbstractVehicle vehicle) { + // I would call my entity manager's flush() method here to make sure my entity + // is populated with the right @Version before I let it map into the DTO + } + + @AfterMapping + protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) { + result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) ); + } + + public abstract CarDto toCarDto(Car car); +} + +// Generates something like this: +public class VehicleMapperImpl extends VehicleMapper { + + public CarDto toCarDto(Car car) { + flushEntity( car ); + + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + // attributes mapping ... + + fillTank( car, carDto ); + + return carDto; + } +} +---- +==== + +If the `@BeforeMapping` / `@AfterMapping` method has parameters, the method invocation is only generated if the return type of the method (if non-`void`) is assignable to the return type of the mapping method and all parameters can be *assigned* by the source or target parameters of the mapping method: + +* A parameter annotated with `@MappingTarget` is populated with the target instance of the mapping. +* A parameter annotated with `@TargetType` is populated with the target type of the mapping. +* Parameters annotated with `@Context` are populated with the context parameters of the mapping method. +* Any other parameter is populated with a source parameter of the mapping. + +For non-`void` methods, the return value of the method invocation is returned as the result of the mapping method if it is not `null`. + +As with mapping methods, it is possible to specify type parameters for before/after-mapping methods. + +.Mapper with @AfterMapping hook that returns a non-null value +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public abstract class VehicleMapper { + + @PersistenceContext + private EntityManager entityManager; + + @AfterMapping + protected T attachEntity(@MappingTarget T entity) { + return entityManager.merge(entity); + } + + public abstract CarDto toCarDto(Car car); +} + +// Generates something like this: +public class VehicleMapperImpl extends VehicleMapper { + + public CarDto toCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + // attributes mapping ... + + CarDto target = attachEntity( carDto ); + if ( target != null ) { + return target; + } + + return carDto; + } +} +---- +==== + +All before/after-mapping methods that *can* be applied to a mapping method *will* be used. <> can be used to further control which methods may be chosen and which not. For that, the qualifier annotation needs to be applied to the before/after-method and referenced in `BeanMapping#qualifiedBy` or `IterableMapping#qualifiedBy`. + +The order of the method invocation is determined primarily by their variant: + +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: + +1. Methods declared on `@Context` parameters, ordered by the parameter order. +2. Methods implemented in the mapper itself. +3. Methods from types referenced in `Mapper#uses()`, in the order of the type declaration in the annotation. +4. Methods declared in one type are used after methods declared in their super-type. + +*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation. + +[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 new file mode 100644 index 0000000000..51b2bbff3c --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc @@ -0,0 +1,424 @@ +[[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. +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class GolfPlayer { + + private double handicap; + private String name; + + public double handicap() { + return handicap; + } + + public GolfPlayer withHandicap(double handicap) { + this.handicap = handicap; + return this; + } + + public String name() { + return name; + } + + public GolfPlayer withName(String name) { + this.name = name; + return this; + } +} +---- +==== + +.Source object GolfPlayerDto with fluent API. +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class GolfPlayerDto { + + private double handicap; + private String name; + + public double handicap() { + return handicap; + } + + public GolfPlayerDto withHandicap(double handicap) { + this.handicap = handicap; + return this; + } + + public String name() { + return name; + } + + public GolfPlayerDto withName(String name) { + this.name = name; + return this; + } +} +---- +==== + +We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this: + +.Source object with fluent API. +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface GolfPlayerMapper { + + GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class ); + + GolfPlayerDto toDto(GolfPlayer player); + + GolfPlayer toPlayer(GolfPlayerDto player); + +} +---- +==== + +This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`: + +.CustomAccessorNamingStrategy +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +/** + * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the + * form of {@code withProperty(value)}. + */ +public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy { + + @Override + public boolean isGetterMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID; + } + + @Override + public boolean isSetterMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return methodName.startsWith( "with" ) && methodName.length() > 4; + } + + @Override + public String getPropertyName(ExecutableElement getterOrSetterMethod) { + String methodName = getterOrSetterMethod.getSimpleName().toString(); + return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); + } +} +---- +==== +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. + +[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 + +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. + +[NOTE] +==== +The `DefaultMappingExclusionProvider` will exclude all types under the `java` or `javax` packages. +This means that MapStruct will not try to generate an automatic sub-mapping method between some custom type and some type declared in the Java class library. +==== + +.Source object +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/nestedbeans/exclusions/custom/Source.java[tag=documentation] +---- +==== + +.Target object +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/nestedbeans/exclusions/custom/Target.java[tag=documentation] +---- +==== + +.Mapper definition +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/nestedbeans/exclusions/custom/ErroneousCustomExclusionMapper.java[tag=documentation] +---- +==== + +We want to exclude the `NestedTarget` from the automatic sub-mapping method generation. + +.CustomMappingExclusionProvider +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusionProvider.java[tag=documentation] +---- +==== + + +[[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. + +.Custom Builder Provider which disables Builder support +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +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_` +and the default value for them when mapping from `null` is `UNSPECIFIED` + +.Normal Enum +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public enum CheeseType { + BRIE, + ROQUEFORT; +} +---- +==== + +.Custom marker enum +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public enum CustomCheeseType implements CustomEnumMarker { + + UNSPECIFIED, + CUSTOM_BRIE, + CUSTOM_ROQUEFORT; +} +---- +==== + +We want `CheeseType` and `CustomCheeseType` to be mapped without the need to manually define the value mappings: + +.Custom enum mapping +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CheeseTypeMapper { + + CheeseType map(CustomCheeseType cheese); + + CustomCheeseType map(CheeseType cheese); +} +---- +==== + +This can be achieved with implementing the SPI `org.mapstruct.ap.spi.EnumMappingStrategy` as in the following example. +Here’s an implemented `org.mapstruct.ap.spi.EnumMappingStrategy`: + +.Custom enum naming strategy +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy { + + @Override + public String getDefaultNullEnumConstant(TypeElement enumType) { + if ( isCustomEnum( enumType ) ) { + return "UNSPECIFIED"; + } + + return super.getDefaultNullEnumConstant( enumType ); + } + + @Override + public String getEnumConstant(TypeElement enumType, String enumConstant) { + if ( isCustomEnum( enumType ) ) { + return getCustomEnumConstant( enumConstant ); + } + return super.getEnumConstant( enumType, enumConstant ); + } + protected String getCustomEnumConstant(String enumConstant) { + if ( "UNSPECIFIED".equals( enumConstant ) ) { + return MappingConstantsGem.NULL; + } + return enumConstant.replace( "CUSTOM_", "" ); + } + protected boolean isCustomEnum(TypeElement enumType) { + for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) { + if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) { + return true; + } + } + return false; + } +} +---- +==== + +The generated code then for the `CheeseMapper` looks like: + +.Generated CheeseTypeMapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class CheeseTypeMapperImpl implements CheeseTypeMapper { + + @Override + public CheeseType map(CustomCheeseType cheese) { + if ( cheese == null ) { + return null; + } + + CheeseType cheeseType; + + switch ( cheese ) { + case UNRECOGNIZED: cheeseType = null; + break; + case CUSTOM_BRIE: cheeseType = CheeseType.BRIE; + break; + case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT; + break; + default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese ); + } + + return cheeseType; + } + + @Override + public CustomCheeseType map(CheeseType cheese) { + if ( cheese == null ) { + return CustomCheeseType.UNSPECIFIED; + } + + CustomCheeseType customCheeseType; + + switch ( cheese ) { + case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE; + break; + case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT; + break; + default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese ); + } + + return customCheeseType; + } +} +---- +==== + +[[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. + +.Custom Enum Transformation Strategy which lower-cases the value and applies a suffix +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +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 new file mode 100644 index 0000000000..ef2a58f82e --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc @@ -0,0 +1,192 @@ +[[third-party-api-integration]] +== Third-party API integration + +[[non-shipped-annotations]] +=== Non-shipped annotations + +There are various use-cases you must resolve ambiguity for MapStruct to use a correct piece of code. +However, the primary goal of MapStruct is to focus on bean mapping without polluting the entity code. +For that reason, MapStruct is flexible enough to interact with already defined annotations from third-party libraries. +The requirement to enable this behavior is to match the _name_ of such annotation. +Hence, we say that annotation can be _from any package_. + +The annotations _named_ `@ConstructorProperties` and `@Default` are currently examples of this kind of annotation. + +[WARNING] +==== +If such named third-party annotation exists, it does not guarantee its `@Target` matches with the intended placement. +Be aware of placing a third-party annotation just for sake of mapping is not recommended as long as it might lead to unwanted side effects caused by that library. +==== + +A very common case is that no third-party dependency imported to your project provides such annotation or is inappropriate for use as already described. +In such cases create your own annotation, for example: + +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +package foo.support.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.CONSTRUCTOR) +@Retention(RetentionPolicy.CLASS) +public @interface Default { + +} +---- +==== + +[[lombok]] +=== Lombok + +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. + +[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. +This resolves the compilation issues of Lombok and MapStruct modules. + +[source, xml] +---- + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + +---- +==== + +==== Set up + +The set up using Maven or Gradle does not differ from what is described in <>. Additionally, you need to provide Lombok dependencies. + +.Maven configuration +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- + + + {mapstructVersion} + 1.18.16 + 1.8 + 1.8 + + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + org.projectlombok + lombok + ${org.projectlombok.version} + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + +---- +==== + +.Gradle configuration (3.4 and later) +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- + +dependencies { + + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + compileOnly "org.projectlombok:lombok:1.18.16" + annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0" + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.projectlombok:lombok:1.18.16" +} + +---- +==== + +The usage combines what you already know from <> and Lombok. + +.Usage of MapStruct with Lombok +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Data +public class Source { + + private String test; +} + +public class Target { + + private Long testing; + + public Long getTesting() { + return testing; + } + + public void setTesting( Long testing ) { + this.testing = testing; + } +} + +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping( source = "test", target = "testing" ) + Target toTarget( Source s ); +} + +---- +==== + +A working example can be found on the GitHub project https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-lombok[mapstruct-lombok]. diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc new file mode 100644 index 0000000000..31479f8a20 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -0,0 +1,389 @@ +[[setup]] +== Set up + +MapStruct is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. + +It comprises the following artifacts: + +* _org.mapstruct:mapstruct_: contains the required annotations such as `@Mapping` +* _org.mapstruct:mapstruct-processor_: contains the annotation processor which generates mapper implementations + +=== Apache Maven + +For Maven based projects add the following to your POM file in order to use MapStruct: + +.Maven configuration +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + {mapstructVersion} + +... + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + +... + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + +... +---- +==== + +[TIP] +==== +If you are working with the Eclipse IDE, make sure to have a current version of the http://www.eclipse.org/m2e/[M2E plug-in]. +When importing a Maven project configured as shown above, it will set up the MapStruct annotation processor so it runs right in the IDE, whenever you save a mapper type. +Neat, isn't it? + +To double check that everything is working as expected, go to your project's properties and select "Java Compiler" -> "Annotation Processing" -> "Factory Path". +The MapStruct processor JAR should be listed and enabled there. +Any processor options configured via the compiler plug-in (see below) should be listed under "Java Compiler" -> "Annotation Processing". + +If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled. +To do so, go to "Preferences" -> "Maven" -> "Annotation Processing" and select "Automatically configure JDT APT". +Alternatively, specify the following in the `properties` section of your POM file: `jdt_apt`. + +Also make sure that your project is using Java 1.8 or later (project properties -> "Java Compiler" -> "Compile Compliance Level"). +It will not work with older versions. +==== + +=== Gradle + +Add the following to your Gradle build file in order to enable MapStruct: + +.Gradle configuration +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- +... +plugins { + ... + id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse +} + +dependencies { + ... + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + + // If you are using mapstruct in test code + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" +} +... +---- +==== + +You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-gradle[mapstruct-examples] project on GitHub. + + +=== Apache Ant + +Add the `javac` task configured as follows to your _build.xml_ file in order to enable MapStruct in your Ant-based project. Adjust the paths as required for your project layout. + +.Ant configuration +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + + + +... +---- +==== + +You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-ant[mapstruct-examples] project on GitHub. + +[[configuration-options]] +=== Configuration options + +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 `compilerArgs` within the configuration of the Maven processor plug-in like this: + +.Maven configuration +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + true + + + -Amapstruct.suppressGeneratorTimestamp=true + + + -Amapstruct.suppressGeneratorVersionInfoComment=true + + + -Amapstruct.verbose=true + + + + +... +---- +==== + +.Gradle configuration +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- +... +compileJava { + options.compilerArgs += [ + '-Amapstruct.suppressGeneratorTimestamp=true', + '-Amapstruct.suppressGeneratorVersionInfoComment=true', + '-Amapstruct.verbose=true' + ] +} +... +---- +==== + +The following options exist: + +.MapStruct processor options +[cols="1,2a,1"] +|=== +|Option|Purpose|Default + +|`mapstruct. +suppressGeneratorTimestamp` +|If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed. +|`false` + +|`mapstruct.verbose` +|If set to `true`, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also `showWarnings` needs to be added due to a problem in the maven-compiler-plugin configuration. +|`false` + +|`mapstruct. +suppressGeneratorVersionInfoComment` +|If set to `true`, the creation of the `comment` attribute in the `@Generated` annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing. +|`false` + +|`mapstruct.defaultComponentModel` +|The name of the component model (see <>) based on which mappers should be generated. + +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 (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` (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` + +|`mapstruct.defaultInjectionStrategy` +| The type of the injection in mapper via parameter `uses`. This is only used on annotated based component models + such as CDI, Spring and JSR 330. + +Supported values are: + +* `field`: dependencies will be injected in fields +* `constructor`: will be generated constructor. Dependencies will be injected via constructor. + +When CDI `componentModel` a default constructor will also be generated. +If a injection strategy is given for a specific mapper via `@Mapper#injectionStrategy()`, the value from the annotation takes precedence over the option. +|`field` + +|`mapstruct.unmappedTargetPolicy` +|The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value. + +Supported values are: + +* `ERROR`: any unmapped target property will cause the mapping code generation to fail +* `WARN`: any unmapped target property will cause a warning at build time +* `IGNORE`: unmapped target properties are ignored + +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 + +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 new file mode 100644 index 0000000000..96c76fc20d --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -0,0 +1,1281 @@ +[[defining-mapper]] +== Defining a mapper + +In this section you'll learn how to define a bean mapper with MapStruct and which options you have to do so. + +[[basic-mappings]] +=== Basic mappings + +To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the `org.mapstruct.Mapper` annotation: + +.Java interface to define a mapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "seatCount", source = "numberOfSeats") + CarDto carToCarDto(Car car); + + @Mapping(target = "fullName", source = "name") + PersonDto personToPersonDto(Person person); +} +---- +==== + +The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time. + +In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`): + +* When a property has the same name as its target entity counterpart, it will be mapped implicitly. +* When a property has a different name in the target entity, its name can be specified via the `@Mapping` annotation. + +[TIP] +==== +The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans specification] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`. +==== +[TIP] +==== +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] +==== +Fluent setters are also supported. +Fluent setters are setters that return the same type as the type being modified. + +E.g. + +``` +public Builder seatCount(int seatCount) { + this.seatCount = seatCount; + return this; +} +``` +==== + + +To get a better understanding of what MapStruct does have a look at the following implementation of the `carToCarDto()` method as generated by MapStruct: + +.Code generated by MapStruct +==== +[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.getFeatures() != null ) { + carDto.setFeatures( new ArrayList( car.getFeatures() ) ); + } + carDto.setManufacturer( car.getMake() ); + carDto.setSeatCount( car.getNumberOfSeats() ); + carDto.setDriver( personToPersonDto( car.getDriver() ) ); + carDto.setPrice( String.valueOf( car.getPrice() ) ); + if ( car.getCategory() != null ) { + carDto.setCategory( car.getCategory().toString() ); + } + carDto.setEngine( engineToEngineDto( car.getEngine() ) ); + + return carDto; + } + + @Override + public PersonDto personToPersonDto(Person person) { + //... + } + + private EngineDto engineToEngineDto(Engine engine) { + if ( engine == null ) { + return null; + } + + EngineDto engineDto = new EngineDto(); + + engineDto.setHorsePower(engine.getHorsePower()); + engineDto.setFuel(engine.getFuel()); + + return engineDto; + } +} +---- +==== + +The general philosophy of MapStruct is to generate code which looks as much as possible as if you had written it yourself from hand. In particular this means that the values are copied from source to target by plain getter/setter invocations instead of reflection or similar. + +As the example shows the generated code takes into account any name mappings specified via `@Mapping`. +If the type of a mapped attribute is different in source and target entity, +MapStruct will either apply an automatic conversion (as e.g. for the _price_ property, see also <>) +or optionally invoke / create another mapping method (as e.g. for the _driver_ / _engine_ property, see also <>). +MapStruct will only create a new mapping method if and only if the source and target property are properties of a Bean and they themselves are Beans or simple properties. +i.e. they are not `Collection` or `Map` type properties. + +Collection-typed attributes with the same element type will be copied by creating a new instance of the target collection type containing the elements from the source property. For collection-typed attributes with different element types each element will be mapped individually and added to the target collection (see <>). + +MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types. + +[[mapping-composition]] +=== 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: + +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Retention(RetentionPolicy.CLASS) +@Mapping(target = "id", ignore = true) +@Mapping(target = "creationDate", expression = "java(new java.util.Date())") +@Mapping(target = "name", source = "groupName") +public @interface ToEntity { } +---- +==== + +Can be used to characterise an `Entity` without the need to have a common base type. For instance, `ShelveEntity` and `BoxEntity` do not share a common base type in the `StorageMapper` below. +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface StorageMapper { + + StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class ); + + @ToEntity + @Mapping( target = "weightLimit", source = "maxWeight") + ShelveEntity map(ShelveDto source); + + @ToEntity + @Mapping( target = "label", source = "designation") + BoxEntity map(BoxDto source); +} +---- +==== + +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. + +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 <>). + +[[adding-custom-methods]] +=== Adding custom methods to mappers + +In some cases it can be required to manually implement a specific mapping from one type to another which can't be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct (see <>). + +Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match. + +As an example let's assume the mapping from `Person` to `PersonDto` requires some special logic which can't be generated by MapStruct. You could then define the mapper from the previous example like this: + +.Mapper which defines a custom mapping with a default method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(...) + ... + CarDto carToCarDto(Car car); + + default PersonDto personToPersonDto(Person person) { + //hand-written mapping logic + } +} +---- +==== + +The class generated by MapStruct implements the method `carToCarDto()`. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute. + +A mapper could also be defined in the form of an abstract class instead of an interface and implement the custom methods directly in the mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class. + +The previous example where the mapping from `Person` to `PersonDto` requires some special logic could then be defined like this: + +.Mapper defined by an abstract class +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public abstract class CarMapper { + + @Mapping(...) + ... + public abstract CarDto carToCarDto(Car car); + + public PersonDto personToPersonDto(Person person) { + //hand-written mapping logic + } +} +---- +==== + +MapStruct will generate a sub-class of `CarMapper` with an implementation of the `carToCarDto()` method as it is declared abstract. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute. + +[[mappings-with-several-source-parameters]] +=== Mapping methods with several source parameters + +MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example: + +.Mapping method with several source parameters +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface AddressMapper { + + @Mapping(target = "description", source = "person.description") + @Mapping(target = "houseNumber", source = "address.houseNo") + DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address); +} +---- +==== + +The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name. + +In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically. + +[WARNING] +==== +Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation. +==== + +[TIP] +==== +Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated. +==== + +MapStruct also offers the possibility to directly refer to a source parameter. + +.Mapping method directly referring to a source parameter +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface AddressMapper { + + @Mapping(target = "description", source = "person.description") + @Mapping(target = "houseNumber", source = "hn") + DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn); +} +---- +==== + +In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`. + +[[mapping-nested-bean-properties-to-current-target]] +=== Mapping nested bean properties to current target + +If you don't want explicitly name all properties from nested source bean, you can use `.` as target. + This will tell MapStruct to map every property from source bean to target object. The following shows an example: + +.use of "target this" annotation "." +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- + @Mapper + public interface CustomerMapper { + + @Mapping( target = "name", source = "record.name" ) + @Mapping( target = ".", source = "record" ) + @Mapping( target = ".", source = "account" ) + Customer customerDtoToCustomer(CustomerDto customerDto); + } +---- +==== + +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 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`). + + + +[[updating-bean-instances]] +=== Updating existing bean instances + +In some cases you need mappings which don't create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with `@MappingTarget`. The following shows an example: + +.Update method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + void updateCarFromDto(CarDto carDto, @MappingTarget Car car); +} +---- +==== + +The generated code of the `updateCarFromDto()` method will update the passed `Car` instance with the properties from the given `CarDto` object. There may be only one parameter marked as mapping target. Instead of `void` you may also set the method's return type to the type of the target parameter, which will cause the generated implementation to update the passed mapping target and return it as well. This allows for fluent invocations of mapping methods. + +For `CollectionMappingStrategy.ACCESSOR_ONLY` Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. Otherwise, For `CollectionMappingStrategy.ADDER_PREFERRED` or `CollectionMappingStrategy.TARGET_IMMUTABLE` the target will not be cleared and the values will be populated immediately. + +[[direct-field-mappings]] +=== Mappings with direct field access + +MapStruct also supports mappings of `public` fields that have no getters/setters. MapStruct will +use the fields as read/write accessor if it cannot find suitable getter/setter methods for the property. + +A field is considered as a read accessor if it is `public` or `public final`. If a field is `static` it is not +considered as a read accessor. + +A field is considered as a write accessor only if it is `public`. If a field is `final` and/or `static` it is not +considered as a write accessor. + +Small example: + +.Example classes for mapping +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Customer { + + private Long id; + private String name; + + //getters and setter omitted for brevity +} + +public class CustomerDto { + + public Long id; + public String customerName; +} + +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "name", source = "customerName") + Customer toCustomer(CustomerDto customerDto); + + @InheritInverseConfiguration + CustomerDto fromCustomer(Customer customer); +} +---- +==== + +For the configuration from above, the generated mapper looks like: + +.Generated mapper for example classes +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CustomerMapperImpl implements CustomerMapper { + + @Override + public Customer toCustomer(CustomerDto customerDto) { + // ... + customer.setId( customerDto.id ); + customer.setName( customerDto.customerName ); + // ... + } + + @Override + public CustomerDto fromCustomer(Customer customer) { + // ... + customerDto.id = customer.getId(); + customerDto.customerName = customer.getName(); + // ... + } +} +---- +==== + +You can find the complete example in the +https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping] +project on GitHub. + +[[mapping-with-builders]] +=== Using builders + +MapStruct also supports mapping of immutable types via builders. +When performing a mapping MapStruct checks if there is a builder for the type being mapped. +This is done via the `BuilderProvider` SPI. +If a Builder exists for a certain type, then that builder will be used for the mappings. + +The default implementation of the `BuilderProvider` assumes the following: + +* 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 +then this would be used, otherwise a compilation error would be created. +* A specific build method can be defined by using `@Builder` within: `@BeanMapping`, `@Mapper` or `@MapperConfig` +* In case there are multiple builder creation methods that satisfy the above conditions then a `MoreThanOneBuilderCreationMethodException` +will be thrown from the `DefaultBuilderProvider` SPI. +In case of a `MoreThanOneBuilderCreationMethodException` MapStruct will write a warning in the compilation and not use any builder. + +If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type). +To finish the mapping MapStruct generates code that will invoke the build method of the builder. + +[NOTE] +====== +Builder detection can be switched off by means of `@Builder#disableBuilder`. MapStruct will fall back on regular getters / setters in case builders are disabled. +====== + +[NOTE] +====== +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] +[subs="verbatim,attributes"] +---- +public class Person { + + private final String name; + + protected Person(Person.Builder builder) { + this.name = builder.name; + } + + public static Person.Builder builder() { + return new Person.Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Person create() { + return new Person( this ); + } + } +} +---- +==== + +.Person Mapper definition +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public interface PersonMapper { + + Person map(PersonDto dto); +} +---- +==== + +.Generated mapper with builder +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class PersonMapperImpl implements PersonMapper { + + public Person map(PersonDto dto) { + if (dto == null) { + return null; + } + + Person.Builder builder = Person.builder(); + + builder.name( dto.getName() ); + + return builder.create(); + } +} +---- +==== + +Supported builder frameworks: + +* https://projectlombok.org/[Lombok] - It is required to have the Lombok classes in a separate module. +See for more information at https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] and to set up Lombok with MapStruct, refer to <>. +* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue] +* https://immutables.github.io/[Immutables] - When Immutables are present on the annotation processor path then the `ImmutablesAccessorNamingStrategy` and `ImmutablesBuilderProvider` would be used by default +* https://github.com/google/FreeBuilder[FreeBuilder] - When FreeBuilder is present on the annotation processor path then the `FreeBuilderAccessorNamingStrategy` would be used by default. +When using FreeBuilder then the JavaBean convention should be followed, otherwise MapStruct won't recognize the fluent getters. +* It also works for custom builders (handwritten ones) if the implementation supports the defined rules for the default `BuilderProvider`. +Otherwise, you would need to write a custom `BuilderProvider` + +[TIP] +==== +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]] +=== Using Constructors + +MapStruct supports using constructors for mapping target types. +When doing a mapping MapStruct checks if there is a builder for the type being mapped. +If there is no builder, then MapStruct looks for a single accessible constructor. +When there are multiple constructors then the following is done to pick the one which should be used: + +* If a constructor is annotated with an annotation _named_ `@Default` (from any package, see <>) it will be used. +* If a single public constructor exists then it will be used to construct the object, and the other non public constructors will be ignored. +* If a parameterless constructor exists then it will be used to construct the object, and the other constructors will be ignored. +* If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotation _named_ `@Default` (from any package, see <>) can used. + +.Deciding which constructor to use +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Vehicle { + + protected Vehicle() { } + + // MapStruct will use this constructor, because it is a single public constructor + public Vehicle(String color) { } +} + +public class Car { + + // MapStruct will use this constructor, because it is a parameterless empty constructor + public Car() { } + + public Car(String make, String color) { } +} + +public class Truck { + + public Truck() { } + + // MapStruct will use this constructor, because it is annotated with @Default + @Default + public Truck(String make, String color) { } +} + +public class Van { + + // There will be a compilation error when using this class because MapStruct cannot pick a constructor + + public Van(String make) { } + + public Van(String make, String color) { } + +} +---- +==== + +When using a constructor then the names of the parameters of the constructor will be used and matched to the target properties. +When the constructor has an annotation _named_ `@ConstructorProperties` (from any package, see <>) then this annotation will be used to get the names of the parameters. + +[NOTE] +==== +When an object factory method or a method annotated with `@ObjectFactory` exists, it will take precedence over any constructor defined in the target. +The target object constructor will not be used in that case. +==== + + +.Person with constructor parameters +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Person { + + private final String name; + private final String surname; + + public Person(String name, String surname) { + this.name = name; + this.surname = surname; + } +} +---- +==== + +.Person With Constructor Mapper definition +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public interface PersonMapper { + + Person map(PersonDto dto); +} +---- +==== + +.Generated mapper with constructor +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class PersonMapperImpl implements PersonMapper { + + public Person map(PersonDto dto) { + if (dto == null) { + return null; + } + + String name; + String surname; + name = dto.getName(); + surname = dto.getSurname(); + + Person person = new Person( name, surname ); + + return person; + } +} +---- +==== + +[[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 + +There are situations when a mapping from a `Map` into a specific bean is needed. +MapStruct offers a transparent way of doing such a mapping by using the target bean properties (or defined through `Mapping#source`) to extract the values from the map. +Such a mapping looks like: + +.Example classes for mapping map to bean +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Customer { + + private Long id; + private String name; + + //getters and setter omitted for brevity +} + +@Mapper +public interface CustomerMapper { + + @Mapping(target = "name", source = "customerName") + Customer toCustomer(Map map); + +} +---- +==== + +.Generated mapper for mapping map to bean +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CustomerMapperImpl implements CustomerMapper { + + @Override + public Customer toCustomer(Map map) { + // ... + if ( map.containsKey( "id" ) ) { + customer.setId( Integer.parseInt( map.get( "id" ) ) ); + } + if ( map.containsKey( "customerName" ) ) { + customer.setName( map.get( "customerName" ) ); + } + // ... + } +} +---- +==== + +[NOTE] +==== +All existing rules about mapping between different types and using other mappers defined with `Mapper#uses` or custom methods in the mappers are applied. +i.e. You can map from `Map` where for each property a conversion from `Integer` into the respective property will be needed. +==== + +[WARNING] +==== +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 new file mode 100644 index 0000000000..ec817e0cdf --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -0,0 +1,153 @@ +[[retrieving-mapper]] +== Retrieving a mapper + +[[mappers-factory]] +=== The Mappers factory (no dependency injection) + +When not using a DI framework, Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return: + +.Using the Mappers factory +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +CarMapper mapper = Mappers.getMapper( CarMapper.class ); +---- +==== + +By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type: + +.Declaring an instance of a mapper (interface) +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + CarDto carToCarDto(Car car); +} + +---- +==== + +.Declaring an instance of a mapper (abstract class) +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public abstract class CarMapper { + + public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + CarDto carToCarDto(Car car); +} + +---- +==== + +This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances: + +.Accessing a mapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +Car car = ...; +CarDto dto = CarMapper.INSTANCE.carToCarDto( car ); +---- +==== + + +Note that mappers generated by MapStruct are stateless and thread-safe and thus can safely be accessed from several threads at the same time. + +[[using-dependency-injection]] +=== Using dependency injection + +If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection and *not* via the `Mappers` class as described above. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <>. + +Currently there is support for CDI and Spring (the latter either via its custom annotations or using the JSR 330 annotations). See <> for the allowed values of the `componentModel` attribute which are the same as for the `mapstruct.defaultComponentModel` processor option and constants are defined in a class `MappingConstants.ComponentModel`. In both cases the required annotations will be added to the generated mapper implementations classes in order to make the same subject to dependency injection. The following shows an example using CDI: + +.A mapper using the CDI component model +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(componentModel = MappingConstants.ComponentModel.CDI) +public interface CarMapper { + + CarDto carToCarDto(Car car); +} + +---- +==== + +The generated mapper implementation will be marked with the `@ApplicationScoped` annotation and thus can be injected into fields, constructor arguments etc. using the `@Inject` annotation: + +.Obtaining a mapper via dependency injection +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Inject +private CarMapper mapper; +---- +==== + +A mapper which uses other mapper classes (see <>) will obtain these mappers using the configured component model. So if `CarMapper` from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well. + +[[injection-strategy]] +=== Injection strategy + +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 +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface CarMapper { + CarDto carToCarDto(Car car); +} +---- +==== + +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. +==== + +[[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 new file mode 100644 index 0000000000..10c41506de --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -0,0 +1,842 @@ +[[datatype-conversions]] +== Data type conversions + +Not always a mapped attribute has the same type in the source and target objects. For instance an attribute may be of type `int` in the source bean but of type `Long` in the target bean. + +Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class `Car` might have a property `driver` of the type `Person` which needs to be converted into a `PersonDto` object when mapping a `Car` object. + +In this section you'll learn how MapStruct deals with such data type conversions. + +[[implicit-type-conversions]] +=== Implicit type conversions + +MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of type `int` in the source bean but of type `String` in the target bean, the generated code will transparently perform a conversion by calling `String#valueOf(int)` and `Integer#parseInt(String)`, respectively. + +Currently the following conversions are applied automatically: + +* Between all Java primitive data types and their corresponding wrapper types, e.g. between `int` and `Integer`, `boolean` and `Boolean` etc. The generated code is `null` aware, i.e. when converting a wrapper type into the corresponding primitive type a `null` check will be performed. + +* Between all Java primitive number types and the wrapper types, e.g. between `int` and `long` or `byte` and `Integer`. + +[WARNING] +==== +Converting from larger data types to smaller ones (e.g. from `long` to `int`) can cause a value or precision loss. The `Mapper` and `MapperConfig` annotations have a method `typeConversionPolicy` to control warnings / errors. Due to backward compatibility reasons the default value is `ReportingPolicy.IGNORE`. +==== + +* Between all Java primitive types (including their wrappers) and `String`, e.g. between `int` and `String` or `Boolean` and `String`. A format string as understood by `java.text.DecimalFormat` can be specified. + +.Conversion from int to String +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(source = "price", numberFormat = "$#.00") + CarDto carToCarDto(Car car); + + @IterableMapping(numberFormat = "$#.00") + List prices(List prices); +} +---- +==== +* 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 +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(source = "power", numberFormat = "#.##E0") + CarDto carToCarDto(Car car); + +} +---- +==== + + +* Between `JAXBElement` and `T`, `List>` and `List` + +* Between `java.util.Calendar`/`java.util.Date` and JAXB's `XMLGregorianCalendar` + +* Between `java.util.Date`/`XMLGregorianCalendar` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option as this: + +.Conversion from Date to String +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dd.MM.yyyy") + List stringListToDateList(List dates); +} +---- +==== + +* Between Jodas `org.joda.time.DateTime`, `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate`, `org.joda.time.LocalTime` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). + +* Between Jodas `org.joda.time.DateTime` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Calendar`. + +* Between Jodas `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Date`. + +* Between `java.time.LocalDate`, `java.time.LocalDateTime` and `javax.xml.datatype.XMLGregorianCalendar`. + +* Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). + +* Between `java.time.Instant`, `java.time.Duration`, `java.time.Period` from Java 8 Date-Time package and `String` using the `parse` method in each class to map from `String` and using `toString` to map into `String`. + +* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Date` where, when mapping a `ZonedDateTime` from a given `Date`, the system default timezone is used. + +* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone. + +* Between `java.time.LocalDate` from Java 8 Date-Time package and `java.util.Date` / `java.sql.Date` where timezone UTC is used as the timezone. + +* 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` + +* Between `java.sql.Time` and `java.util.Date` + +* Between `java.sql.Timestamp` and `java.util.Date` + +* When converting from a `String`, omitting `Mapping#dateFormat`, it leads to usage of the default pattern and date format symbols for the default locale. An exception to this rule is `XmlGregorianCalendar` which results in parsing the `String` according to http://www.w3.org/TR/xmlschema-2/#dateTime[XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation]. + +* Between `java.util.Currency` and `String`. +** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/ISO_4217[ISO-4217] alphabetic code otherwise an `IllegalArgumentException` is thrown. + +* Between `java.util.UUID` and `String`. +** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/Universally_unique_identifier[UUID] otherwise an `IllegalArgumentException` is thrown. + +* 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 + +Typically an object has not only primitive attributes but also references other objects. E.g. the `Car` class could contain a reference to a `Person` object (representing the car's driver) which should be mapped to a `PersonDto` object referenced by the `CarDto` class. + +In this case just define a mapping method for the referenced object type as well: + +.Mapper with one mapping method using another +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car); + + PersonDto personToPersonDto(Person person); +} +---- +==== + +The generated code for the `carToCarDto()` method will invoke the `personToPersonDto()` method for mapping the `driver` attribute, while the generated implementation for `personToPersonDto()` performs the mapping of person objects. + +That way it is possible to map arbitrary deep object graphs. When mapping from entities into data transfer objects it is often useful to cut references to other entities at a certain point. To do so, implement a custom mapping method (see the next section) which e.g. maps a referenced entity to its id in the target object. + +When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object: + +. If source and target attribute have the same type, the value will be simply copied *direct* from source to target. If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target attribute. +. If source and target attribute type differ, check whether there is another *mapping method* which has the type of the source attribute as parameter type and the type of the target attribute as return type. If such a method exists it will be invoked in the generated mapping implementation. +. If no such method exists MapStruct will look whether a *built-in conversion* for the source and target type of the attribute exists. If this is the case, the generated mapping code will apply this conversion. +. If no such method exists MapStruct will apply *complex* conversions: +.. mapping method, the result mapped by mapping method, like this: `target = method1( method2( source ) )` +.. built-in conversion, the result mapped by mapping method, like this: `target = method( conversion( source ) )` +.. mapping method, the result mapped by build-in conversion, like this: `target = conversion( method( source ) )` +. If no such method was found MapStruct will try to generate an automatic sub-mapping method that will do the mapping between the source and target attributes. +. If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path. + +A mapping control (`MappingControl`) can be defined on all levels (`@MapperConfig`, `@Mapper`, `@BeanMapping`, `@Mapping`), the latter taking precedence over the former. For example: `@Mapper( mappingControl = NoComplexMapping.class )` takes precedence over `@MapperConfig( mappingControl = DeepClone.class )`. `@IterableMapping` and `@MapMapping` work similar as `@Mapping`. MappingControl is experimental from MapStruct 1.4. +`MappingControl` has an enum that corresponds to the first 4 options above: `MappingControl.Use#DIRECT`, `MappingControl.Use#MAPPING_METHOD`, `MappingControl.Use#BUILT_IN_CONVERSION` and `MappingControl.Use#COMPLEX_MAPPING` the presence of which allows the user to switch *on* a option. The absence of an enum switches *off* a mapping option. Default they are all present enabling all mapping options. + +[NOTE] +==== +In order to stop MapStruct from generating automatic sub-mapping methods as in 5. above, one can use `@Mapper( disableSubMappingMethodsGeneration = true )`. +==== + +[TIP] +==== +The user has full control over the mapping by means of meta annotations. Some handy ones have been defined such as `@DeepClone` which only allows direct mappings. The result: if source and target type are the same, MapStruct will make a deep clone of the source. Sub-mappings-methods have to be allowed (default option). +==== + +[NOTE] +==== +During the generation of automatic sub-mapping methods <> will not be taken into consideration, yet. +Follow issue https://github.com/mapstruct/mapstruct/issues/1086[#1086] for more information. +==== + +[NOTE] +==== +Constructor properties of the target object are also considered as target properties. +You can read more about that in <> +==== + +[[controlling-nested-bean-mappings]] +=== Controlling nested bean mappings + +As explained above, MapStruct will generate a method based on the name of the source and target property. Unfortunately, in many occasions these names do not match. + +The ‘.’ notation in an `@Mapping` source or target type can be used to control how properties should be mapped when names do not match. +There is an elaborate https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-nested-bean-mappings[example] in our examples repository to explain how this problem can be overcome. + +In the simplest scenario there’s a property on a nested level that needs to be corrected. +Take for instance a property `fish` which has an identical name in `FishTankDto` and `FishTank`. +For this property MapStruct automatically generates a mapping: `FishDto fishToFishDto(Fish fish)`. +MapStruct cannot possibly be aware of the deviating properties `kind` and `type`. +Therefore this can be addressed in a mapping rule: `@Mapping(target="fish.kind", source="fish.type")`. +This tells MapStruct to deviate from looking for a name `kind` at this level and map it to `type`. + +.Mapper controlling nested beans mappings I +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface FishTankMapper { + + @Mapping(target = "fish.kind", source = "fish.type") + @Mapping(target = "fish.name", ignore = true) + @Mapping(target = "ornament", source = "interior.ornament") + @Mapping(target = "material.materialType", source = "material") + @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName") + FishTankDto map( FishTank source ); +} +---- +==== + +The same constructs can be used to ignore certain properties at a nesting level, as is demonstrated in the second `@Mapping` rule. + +MapStruct can even be used to “cherry pick” properties when source and target do not share the same nesting level (the same number of properties). +This can be done in the source – and in the target type. This is demonstrated in the next 2 rules: `@Mapping(target="ornament", source="interior.ornament")` and `@Mapping(target="material.materialType", source="material")`. + +The latter can even be done when mappings first share a common base. +For example: all properties that share the same name of `Quality` are mapped to `QualityDto`. +Likewise, all properties of `Report` are mapped to `ReportDto`, with one exception: `organisation` in `OrganisationDto` is left empty (since there is no organization at the source level). +Only the `name` is populated with the `organisationName` from `Report`. +This is demonstrated in `@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")` + +Coming back to the original example: what if `kind` and `type` would be beans themselves? +In that case MapStruct would again generate a method continuing to map. +Such is demonstrated in the next example: + + +.Mapper controlling nested beans mappings II +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface FishTankMapperWithDocument { + + @Mapping(target = "fish.kind", source = "fish.type") + @Mapping(target = "fish.name", expression = "java(\"Jaws\")") + @Mapping(target = "plant", ignore = true ) + @Mapping(target = "ornament", ignore = true ) + @Mapping(target = "material", ignore = true) + @Mapping(target = "quality.document", source = "quality.report") + @Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" ) + FishTankWithNestedDocumentDto map( FishTank source ); + +} +---- +==== + +Note what happens in `@Mapping(target="quality.document", source="quality.report")`. +`DocumentDto` does not exist as such on the target side. It is mapped from `Report`. +MapStruct continues to generate mapping code here. That mapping itself can be guided towards another name. +This even works for constants and expression. Which is shown in the final example: `@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc")`. + +MapStruct will perform a null check on each nested property in the source. + +[TIP] +==== +Instead of configuring everything via the parent method we encourage users to explicitly write their own nested methods. +This puts the configuration of the nested mapping into one place (method) where it can be reused from several methods in the upper level, +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`. +This means that it is possible for MapStruct not to report unmapped target properties in nested mappings. +==== + + +[[invoking-custom-mapping-method]] +=== Invoking custom mapping method + +Sometimes mappings are not straightforward and some fields require custom logic. + +The example below demonstrates how the properties `length`, `width` and `height` in `FishTank` can be mapped to the `VolumeDto` bean, which is a member of `FishTankWithVolumeDto`. `VolumeDto` contains the properties `volume` and `description`. Custom logic is achieved by defining a method which takes `FishTank` instance as a parameter and returns a `VolumeDto`. MapStruct will take the entire parameter `source` and generate code to call the custom method `mapVolume` in order to map the `FishTank` object to the target property `volume`. + +The remainder of the fields could be mapped the regular way: using mappings defined defined by means of `@Mapping` annotations. + +.Manually implemented mapping method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class FishTank { + Fish fish; + String material; + Quality quality; + int length; + int width; + int height; +} + +public class FishTankWithVolumeDto { + FishDto fish; + MaterialDto material; + QualityDto quality; + VolumeDto volume; +} + +public class VolumeDto { + int volume; + String description; +} + +@Mapper +public abstract class FishTankMapperWithVolume { + + @Mapping(target = "fish.kind", source = "source.fish.type") + @Mapping(target = "material.materialType", source = "source.material") + @Mapping(target = "quality.document", source = "source.quality.report") + @Mapping(target = "volume", source = "source") + abstract FishTankWithVolumeDto map(FishTank source); + + VolumeDto mapVolume(FishTank source) { + int volume = source.length * source.width * source.height; + String desc = volume < 100 ? "Small" : "Large"; + return new VolumeDto(volume, desc); + } +} +---- +==== + +Note the `@Mapping` annotation where `source` field is equal to `"source"`, indicating the parameter name `source` itself in the method `map(FishTank source)` instead of a (target) property in `FishTank`. + + +[[invoking-other-mappers]] +=== Invoking other mappers + +In addition to methods defined on the same mapper type MapStruct can also invoke mapping methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping methods. This can be useful to structure your mapping code in several classes (e.g. with one mapper type per application module) or if you want to provide custom mapping logic which can't be generated by MapStruct. + +For instance the `Car` class might contain an attribute `manufacturingDate` while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this: + +.Manually implemented mapper class +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class DateMapper { + + public String asString(Date date) { + return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) + .format( date ) : null; + } + + public Date asDate(String date) { + try { + return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) + .parse( date ) : null; + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } +} +---- +==== + +In the `@Mapper` annotation at the `CarMapper` interface reference the `DateMapper` class like this: + +.Referencing another mapper class +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(uses=DateMapper.class) +public interface CarMapper { + + CarDto carToCarDto(Car car); +} +---- +==== + +When generating code for the implementation of the `carToCarDto()` method, MapStruct will look for a method which maps a `Date` object into a String, find it on the `DateMapper` class and generate an invocation of `asString()` for mapping the `manufacturingDate` attribute. + +Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model for `CarMapper`, `DateMapper` would have to be a CDI bean as well. When using the default component model, any hand-written mapper classes to be referenced by MapStruct generated mappers must declare a public no-args constructor in order to be instantiable. + +[[passing-target-type]] +=== Passing the mapping target type to custom mappers + +When having a custom mapper hooked into the generated mapper with `@Mapper#uses()`, an additional parameter of type `Class` (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with `@TargetType` for MapStruct to generate calls that pass the `Class` instance representing the corresponding property type of the target bean. + +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] +[subs="verbatim,attributes"] +---- +@ApplicationScoped // CDI component model +public class ReferenceMapper { + + @PersistenceContext + private EntityManager entityManager; + + public T resolve(Reference reference, @TargetType Class entityClass) { + return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null; + } + + public Reference toReference(BaseEntity entity) { + return entity != null ? new Reference( entity.getPk() ) : null; + } +} + +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = ReferenceMapper.class ) +public interface CarMapper { + + Car carDtoToCar(CarDto carDto); +} +---- +==== + +MapStruct will then generate something like this: + +.Generated code +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +@ApplicationScoped +public class CarMapperImpl implements CarMapper { + + @Inject + private ReferenceMapper referenceMapper; + + @Override + public Car carDtoToCar(CarDto carDto) { + if ( carDto == null ) { + return null; + } + + Car car = new Car(); + + car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) ); + // ... + + return car; + } +} +---- +==== + +[[passing-context]] +=== Passing context or state objects to custom methods + +Additional _context_ or _state_ information can be passed through generated mapping methods to custom methods with `@Context` parameters. Such parameters are passed to other mapping methods, `@ObjectFactory` methods (see <>) or `@BeforeMapping` / `@AfterMapping` methods (see <>) when applicable and can thus be used in custom code. + +`@Context` parameters are searched for `@ObjectFactory` methods, which are called on the provided context parameter value if applicable. + +`@Context` parameters are also searched for `@BeforeMapping` / `@AfterMapping` methods, which are called on the provided context parameter value if applicable. + +*Note:* no `null` checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure that `null` is not passed in that case. + +For generated code to call a method that is declared with `@Context` parameters, the declaration of the mapping method being generated needs to contain at least those (or assignable) `@Context` parameters as well. The generated code will not create new instances of missing `@Context` parameters nor will it pass a literal `null` instead. + +.Using `@Context` parameters for passing data down to hand-written property mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public abstract CarDto toCar(Car car, @Context Locale translationLocale); + +protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) { + // manually implemented logic to translate the OwnerManual with the given Locale +} +---- +==== + +MapStruct will then generate something like this: + +.Generated code +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +public CarDto toCar(Car car, Locale translationLocale) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), translationLocale ); + // more generated mapping code + + return carDto; +} +---- +==== + + +[[mapping-method-resolution]] +=== Mapping method resolution + +When mapping a property from one type to another, MapStruct looks for the most specific method which maps the source type into the target type. The method may either be declared on the same mapper interface or on another mapper which is registered via `@Mapper#uses()`. The same applies for factory methods (see <>). + +The algorithm for finding a mapping or factory method resembles Java's method resolution algorithm as much as possible. In particular, methods with a more specific source type will take precedence (e.g. if there are two methods, one which maps the searched source type, and another one which maps a super-type of the same). In case more than one most-specific method is found, an error will be raised. + +[TIP] +==== +When working with JAXB, e.g. when converting a `String` to a corresponding `JAXBElement`, MapStruct will take the `scope` and `name` attributes of `@XmlElementDecl` annotations into account when looking for a mapping method. This makes sure that the created `JAXBElement` instances will have the right QNAME value. You can find a test which maps JAXB objects https://github.com/mapstruct/mapstruct/blob/{mapstructVersion}/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java[here]. +==== + +[[selection-based-on-qualifiers]] +=== Mapping method selection based on qualifiers + +In many occasions one requires mapping methods with the same method signature (apart from the name) that have different behavior. +MapStruct has a handy mechanism to deal with such situations: `@Qualifier` (`org.mapstruct.Qualifier`). +A ‘qualifier’ is a custom annotation that the user can write, ‘stick onto’ a mapping method which is included as used mapper +and can be referred to in a bean property mapping, iterable mapping or map mapping. +Multiple qualifiers can be ‘stuck onto’ a method and mapping. + +So, let's say there is a hand-written method to map titles with a `String` return type and `String` argument amongst many other referenced mappers with the same `String` return type - `String` argument signature: + +.Several mapping methods with identical source and target types +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Titles { + + public String translateTitleEG(String title) { + // some mapping logic + } + + public String translateTitleGE(String title) { + // some mapping logic + } +} +---- +==== + +And a mapper using this handwritten mapper, in which source and target have a property 'title' that should be mapped: + +.Mapper causing an ambiguous mapping method error +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper( uses = Titles.class ) +public interface MovieMapper { + + GermanRelease toGerman( OriginalRelease movies ); + +} +---- +==== + +Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2 qualifying methods are found (`translateTitleEG`, `translateTitleGE`) and MapStruct would not have a hint which one to choose. + +Enter the qualifier approach: + +.Declaring a qualifier type +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface TitleTranslator { +} +---- +==== + +And, some qualifiers to indicate which translator to use to map from source language to target language: + +.Declaring qualifier types for mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface EnglishToGerman { +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface GermanToEnglish { +} +---- +==== + +Please take note of the target `TitleTranslator` on type level, `EnglishToGerman`, `GermanToEnglish` on method level! + +Then, using the qualifiers, the mapping could look like this: + +.Mapper using qualifiers +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper( uses = Titles.class ) +public interface MovieMapper { + + @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } ) + GermanRelease toGerman( OriginalRelease movies ); + +} +---- +==== + +.Custom mapper qualifying the methods it provides +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@TitleTranslator +public class Titles { + + @EnglishToGerman + public String translateTitleEG(String title) { + // some mapping logic + } + + @GermanToEnglish + public String translateTitleGE(String title) { + // some mapping logic + } +} +---- +==== + +[WARNING] +==== +Please make sure the used retention policy equals retention policy `CLASS` (`@Retention(RetentionPolicy.CLASS)`). +==== + +[WARNING] +==== +A class / method annotated with a qualifier will not qualify anymore for mappings that do not have the `qualifiedBy` element. +==== + +[TIP] +==== +The same mechanism is also present on bean mappings: `@BeanMapping#qualifiedBy`: it selects the factory method marked with the indicated qualifier. +==== + +In many occasions, declaring a new annotation to aid the selection process can be too much for what you try to achieve. For those situations, MapStruct has the `@Named` annotation. This annotation is a pre-defined qualifier (annotated with `@Qualifier` itself) and can be used to name a Mapper or, more directly a mapping method by means of its value. The same example above would look like: + +.Custom mapper, annotating the methods to qualify by means of `@Named` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Named("TitleTranslator") +public class Titles { + + @Named("EnglishToGerman") + public String translateTitleEG(String title) { + // some mapping logic + } + + @Named("GermanToEnglish") + public String translateTitleGE(String title) { + // some mapping logic + } +} +---- +==== + +.Mapper using named +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper( uses = Titles.class ) +public interface MovieMapper { + + @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } ) + GermanRelease toGerman( OriginalRelease movies ); + +} +---- +==== + +[WARNING] +==== +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 new file mode 100644 index 0000000000..4510c82cc0 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -0,0 +1,231 @@ +[[mapping-collections]] +== Mapping collections + +The mapping of collection types (`List`, `Set` etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface. MapStruct supports a wide range of iterable types from the http://docs.oracle.com/javase/tutorial/collections/intro/index.html[Java Collection Framework]. + +The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection. If a mapping method for the collection element types is found in the given mapper or the mapper it uses, this method is invoked to perform the element conversion. Alternatively, if an implicit conversion for the source and target element types exists, this conversion routine will be invoked. The following shows an example: + +.Mapper with collection mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + Set integerSetToStringSet(Set integers); + + List carsToCarDtos(List cars); + + CarDto carToCarDto(Car car); +} +---- +==== + +The generated implementation of the `integerSetToStringSet` performs the conversion from `Integer` to `String` for each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained element as shown in the following: + +.Generated collection mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +@Override +public Set integerSetToStringSet(Set integers) { + if ( integers == null ) { + return null; + } + + Set set = new LinkedHashSet(); + + for ( Integer integer : integers ) { + set.add( String.valueOf( integer ) ); + } + + return set; +} + +@Override +public List carsToCarDtos(List cars) { + if ( cars == null ) { + return null; + } + + List list = new ArrayList(); + + for ( Car car : cars ) { + list.add( carToCarDto( car ) ); + } + + return list; +} +---- +==== + +Note that MapStruct will look for a collection mapping method with matching parameter and return type, when mapping a collection-typed attribute of a bean, e.g. from `Car#passengers` (of type `List`) to `CarDto#passengers` (of type `List`). + +.Usage of collection mapping method to map a bean property +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) ); +... +---- +==== + +Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In this case the generated code for mapping such a property invokes its getter and adds all the mapped elements: + +.Usage of an adding method for collection mapping +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) ); +... +---- +==== + +[WARNING] +==== +It is not allowed to declare mapping methods with an iterable source (from a java package) and a non-iterable target or the other way around. An error will be raised when detecting this situation. +==== + +[[mapping-maps]] +=== Mapping maps + +Also map-based mapping methods are supported. The following shows an example: + +.Map mapping method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public interface SourceTargetMapper { + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + Map longDateMapToStringStringMap(Map source); +} +---- +==== + +Similar to iterable mappings, the generated code will iterate through the source map, convert each value and key (either by means of an implicit conversion or by invoking another mapping method) and put them into the target map: + +.Generated implementation of map mapping method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +@Override +public Map stringStringMapToLongDateMap(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap(); + + for ( Map.Entry entry : source.entrySet() ) { + + Long key = Long.parseLong( entry.getKey() ); + Date value; + try { + value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() ); + } + catch( ParseException e ) { + throw new RuntimeException( e ); + } + + map.put( key, value ); + } + + return map; +} +---- +==== + +[[collection-mapping-strategies]] +=== Collection mapping strategies + +MapStruct has a `CollectionMappingStrategy`, with the possible values: `ACCESSOR_ONLY`, `SETTER_PREFERRED`, `ADDER_PREFERRED` and `TARGET_IMMUTABLE`. + +In the table below, the dash `-` indicates a property name. Next, the trailing `s` indicates the plural form. The table explains the options and how they are applied to the presence/absence of a `set-s`, `add-` and / or `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(`@TargetType`) + +|`ACCESSOR_ONLY` +|set-s +|get-s +|set-s +|get-s +|get-s + +|`SETTER_PREFERRED` +|set-s +|add- +|set-s +|get-s +|get-s + +|`ADDER_PREFERRED` +|set-s +|add- +|add- +|get-s +|get-s + +|`TARGET_IMMUTABLE` +|set-s +|exception +|set-s +|exception +|set-s +|=== + +Some background: An `adder` method is typically used in case of http://www.eclipse.org/webtools/dali/[generated (JPA) entities], to add a single element (entity) to an underlying collection. Invoking the adder establishes a parent-child relation between parent - the bean (entity) on which the adder is invoked - and its child(ren), the elements (entities) in the collection. To find the appropriate `adder`, MapStruct will try to make a match between the generic parameter type of the underlying collection and the single argument of a candidate `adder`. When there are more candidates, the plural `setter` / `getter` name is converted to singular and will be used in addition to make a match. + +The option `DEFAULT` should not be used explicitly. It is used to distinguish between an explicit user desire to override the default in a `@MapperConfig` from the implicit Mapstruct choice in a `@Mapper`. The option `DEFAULT` is synonymous to `ACCESSOR_ONLY`. + +[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 initialized collections instead of Mapstruct creating the target entity by its constructor. +==== + +[[implementation-types-for-collection-mappings]] +=== Implementation types used for collection mappings + +When an iterable or map mapping method declares an interface type as return type, one of its implementation types will be instantiated in the generated code. The following table shows the supported interface types and their corresponding implementation types as instantiated in the generated code: + +.Collection mapping implementation types +|=== +|Interface type|Implementation type + +|`Iterable`|`ArrayList` + +|`Collection`|`ArrayList` + +|`List`|`ArrayList` + +|`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-7-mapping-streams.asciidoc b/documentation/src/main/asciidoc/chapter-7-mapping-streams.asciidoc new file mode 100644 index 0000000000..ec41719eb8 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-7-mapping-streams.asciidoc @@ -0,0 +1,67 @@ +[[mapping-streams]] +== Mapping Streams + +The mapping of `java.util.Stream` is done in a similar way as the mapping of collection types, i.e. by defining mapping +methods with the required source and target types in a mapper interface. + +The generated code will contain the creation of a `Stream` from the provided `Iterable`/array or will collect the +provided `Stream` into an `Iterable`/array. If a mapping method or an implicit conversion for the source and target +element types exists, then this conversion will be done in `Stream#map()`. The following shows an example: + +.Mapper with stream mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + Set integerStreamToStringSet(Stream integers); + + List carsToCarDtos(Stream cars); + + CarDto carToCarDto(Car car); +} +---- +==== + +The generated implementation of the `integerStreamToStringSet()` performs the conversion from `Integer` to `String` for +each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained +element as shown in the following: + +.Generated stream mapping methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +@Override +public Set integerStreamToStringSet(Stream integers) { + if ( integers == null ) { + return null; + } + + return integers.map( integer -> String.valueOf( integer ) ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ); +} + +@Override +public List carsToCarDtos(Stream cars) { + if ( cars == null ) { + return null; + } + + return cars.map( car -> carToCarDto( car ) ) + .collect( Collectors.toCollection( ArrayList::new ) ); +} +---- +==== + +[WARNING] +==== +If a mapping from a `Stream` to an `Iterable` or an array is performed, then the passed `Stream` will be consumed +and it will no longer be possible to consume it. +==== + +The same implementation types as in <> are used for the creation of the +collection when doing `Stream` to `Iterable` mapping. diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc new file mode 100644 index 0000000000..fcb353010d --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -0,0 +1,356 @@ +[[mapping-enum-types]] +== Mapping Values + +=== Mapping enum to enum types + +MapStruct supports the generation of methods which map one Java enum type into another. + +By default, each constant from the source enum is mapped to a constant with the same name in the target enum type. If required, a constant from the source enum may be mapped to a constant with another name with help of the `@ValueMapping` annotation. Several constants from the source enum can be mapped to the same constant in the target type. + +The following shows an example: + +.Enum mapping method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface OrderMapper { + + OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); + + @ValueMappings({ + @ValueMapping(target = "SPECIAL", source = "EXTRA"), + @ValueMapping(target = "DEFAULT", source = "STANDARD"), + @ValueMapping(target = "DEFAULT", source = "NORMAL") + }) + ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); +} +---- +==== + +.Enum mapping method result +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class OrderMapperImpl implements OrderMapper { + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType_; + + switch ( orderType ) { + case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL; + break; + case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT; + break; + case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT; + break; + case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; + break; + case B2B: externalOrderType_ = ExternalOrderType.B2B; + break; + default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType ); + } + + return externalOrderType_; + } +} +---- +==== +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. + +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. + +In case of source `` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `` source. + +MapStruct will *not* attempt such name based mapping for `` and directly apply the target specified in the `@ValueMapping` with `` source to the remainder. + +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. +==== + +Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `` and `` will be ignored in that case. + +The following code snippets exemplify the use of the aforementioned constants. + +.Enum mapping method, `` and `` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface SpecialOrderMapper { + + SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class ); + + @ValueMappings({ + @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ), + @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ), + @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" ) + }) + ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); +} +---- +==== + +.Enum mapping method result, `` and `` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class SpecialOrderMapperImpl implements SpecialOrderMapper { + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return ExternalOrderType.DEFAULT; + } + + ExternalOrderType externalOrderType_; + + switch ( orderType ) { + case STANDARD: externalOrderType_ = null; + break; + case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; + break; + case B2B: externalOrderType_ = ExternalOrderType.B2B; + break; + default: externalOrderType_ = ExternalOrderType.SPECIAL; + } + + return externalOrderType_; + } +} +---- +==== + +*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``. + + +.Enum mapping method with `` +==== +[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 + +MapStruct supports enum to a String mapping along the same lines as is described in <>. There are similarities and differences: + +*enum to `String`* + +1. Similarity: All not explicit defined mappings will result in each source enum constant value being mapped a `String` value with the same constant value. +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* + +1. Similarity: All not explicit defined mappings will result in the target enum constant mapped from the `String` value when that matches the target enum constant name. +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 + +When no `@ValueMapping`(s) are defined then each constant from the source enum is mapped to a constant with the same name in the target enum type. +However, there are cases where the source enum needs to be transformed before doing the mapping. +E.g. a suffix needs to be applied to map from the source into the target enum. + +.Enum types +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public enum CheeseType { + + BRIE, + ROQUEFORT +} + +public enum CheeseTypeSuffixed { + + BRIE_TYPE, + ROQUEFORT_TYPE +} +---- +==== + +.Enum mapping method with custom name transformation strategy +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CheeseMapper { + + CheeseMapper INSTANCE = Mappers.getMapper( CheeseMapper.class ); + + @EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE") + CheeseTypeSuffixed map(CheeseType cheese); + + @InheritInverseConfiguration + CheeseType map(CheeseTypeSuffix cheese); +} +---- +==== + +.Enum mapping method with custom name transformation strategy result +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CheeseSuffixMapperImpl implements CheeseSuffixMapper { + + @Override + public CheeseTypeSuffixed map(CheeseType cheese) { + if ( cheese == null ) { + return null; + } + + CheeseTypeSuffixed cheeseTypeSuffixed; + + switch ( cheese ) { + case BRIE: cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE; + break; + case ROQUEFORT: cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE; + break; + default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese ); + } + + return cheeseTypeSuffixed; + } + + @Override + public CheeseType map(CheeseTypeSuffixed cheese) { + if ( cheese == null ) { + return null; + } + + CheeseType cheeseType; + + switch ( cheese ) { + case BRIE_TYPE: cheeseType = CheeseType.BRIE; + break; + case ROQUEFORT_TYPE: cheeseType = CheeseType.ROQUEFORT; + break; + default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese ); + } + + return cheeseType; + } +} +---- +==== + +MapStruct provides the following out of the box enum name transformation strategies: + +* _suffix_ - Applies a suffix on the source enum +* _stripSuffix_ - Strips a suffix from the source enum +* _prefix_ - Applies a prefix on the source enum +* _stripPrefix_ - Strips a prefix from the source enum +* _case_ - Applies case transformation to the source enum. Supported _case_ transformations are: +** _upper_ - Performs upper case transformation to the source enum +** _lower_ - Performs lower case transformation to the source enum +** _capital_ - Performs capitalisation of the first character of every word in the source enum and everything else to lowercase. A word is split by "_" + +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/documentation/src/main/asciidoc/chapter-9-object-factories.asciidoc b/documentation/src/main/asciidoc/chapter-9-object-factories.asciidoc new file mode 100644 index 0000000000..1b1e94c64f --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-9-object-factories.asciidoc @@ -0,0 +1,170 @@ +[[object-factories]] +== Object factories + +By default, the generated code for mapping one bean type into another or updating a bean will call the default constructor to instantiate the target type. + +Alternatively you can plug in custom object factories which will be invoked to obtain instances of the target type. One use case for this is JAXB which creates `ObjectFactory` classes for obtaining new instances of schema types. + +To make use of custom factories register them via `@Mapper#uses()` as described in <>, or implement them directly in your mapper. When creating the target object of a bean mapping, MapStruct will look for a parameterless method, a method annotated with `@ObjectFactory`, or a method with only one `@TargetType` parameter that returns the required target type and invoke this method instead of calling the default constructor: + +.Custom object factories +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class DtoFactory { + + public CarDto createCarDto() { + return // ... custom factory logic + } +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class EntityFactory { + + public T createEntity(@TargetType Class entityClass) { + return // ... custom factory logic + } +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(uses= { DtoFactory.class, EntityFactory.class } ) +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + CarDto carToCarDto(Car car); + + Car carDtoToCar(CarDto carDto); +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +public class CarMapperImpl implements CarMapper { + + private final DtoFactory dtoFactory = new DtoFactory(); + + private final EntityFactory entityFactory = new EntityFactory(); + + @Override + public CarDto carToCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = dtoFactory.createCarDto(); + + //map properties... + + return carDto; + } + + @Override + public Car carDtoToCar(CarDto carDto) { + if ( carDto == null ) { + return null; + } + + Car car = entityFactory.createEntity( Car.class ); + + //map properties... + + return car; + } +} +---- +==== + +.Custom object factories with update methods +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(uses = { DtoFactory.class, EntityFactory.class, CarMapper.class } ) +public interface OwnerMapper { + + OwnerMapper INSTANCE = Mappers.getMapper( OwnerMapper.class ); + + void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto); + + void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner); +} +---- +[source, java, linenums] +[subs="verbatim,attributes"] +---- +//GENERATED CODE +public class OwnerMapperImpl implements OwnerMapper { + + private final DtoFactory dtoFactory = new DtoFactory(); + + private final EntityFactory entityFactory = new EntityFactory(); + + private final OwnerMapper ownerMapper = Mappers.getMapper( OwnerMapper.class ); + + @Override + public void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto) { + if ( owner == null ) { + return; + } + + if ( owner.getCar() != null ) { + if ( ownerDto.getCar() == null ) { + ownerDto.setCar( dtoFactory.createCarDto() ); + } + // update car within ownerDto + } + else { + ownerDto.setCar( null ); + } + + // updating other properties + } + + @Override + public void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner) { + if ( ownerDto == null ) { + return; + } + + if ( ownerDto.getCar() != null ) { + if ( owner.getCar() == null ) { + owner.setCar( entityFactory.createEntity( Car.class ) ); + } + // update car within owner + } + else { + owner.setCar( null ); + } + + // updating other properties + } +} +---- +==== + +In addition, annotating a factory method with `@ObjectFactory` lets you gain access to the mapping sources. +Source objects can be added as parameters in the same way as for mapping method. The `@ObjectFactory` +annotation is necessary to let MapStruct know that the given method is only a factory method. + +.Custom object factories with `@ObjectFactory` + +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class DtoFactory { + + @ObjectFactory + public CarDto createCarDto(Car car) { + return // ... custom factory logic + } +} +---- +==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 81e01bb1f8..1705ed4af1 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -1,2373 +1,50 @@ = MapStruct {mapstructVersion} Reference Guide :revdate: {docdate} :toc: right -:Author: Gunnar Morling, Andreas Gudian, Sjaak Derksen and the MapStruct community +:sectanchors: +:source-highlighter: coderay +:Author: Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community +:processor-dir: ../../../../processor +:processor-ap-test: {processor-dir}/src/test/java/org/mapstruct/ap/test +:processor-ap-main: {processor-dir}/src/main/java/org/mapstruct/ap +:integration-tests-dir: ../../../../integrationtest [[Preface]] == Preface This is the reference documentation of MapStruct, an annotation processor for generating type-safe, performant and dependency-free bean mapping code. -This guide covers all the functionality provided by MapStruct. In case this guide doesn't answer all your questions just join the MapStruct https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users[Google group] to get help. +This guide covers all the functionality provided by MapStruct. In case this guide doesn't answer all your questions just join the MapStruct https://github.com/mapstruct/mapstruct/discussions[GitHub Discussions] to get help. -You found a typo or other error in this guide? Please let us know by opening an issue in the https://github.com/mapstruct/mapstruct[MapStruct GitHub repostory], +You found a typo or other error in this guide? Please let us know by opening an issue in the https://github.com/mapstruct/mapstruct[MapStruct GitHub repository], or, better yet, help the community and send a pull request for fixing it! This work is licensed under the http://creativecommons.org/licenses/by-sa/4.0/[Creative Commons Attribution-ShareAlike 4.0 International License]. :numbered: -[[introduction]] -== Introduction +include::chapter-1-introduction.asciidoc[] -MapStruct is a Java http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html[annotation processor] for the generation of type-safe bean mapping classes. +include::chapter-2-set-up.asciidoc[] -All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar. +include::chapter-3-defining-a-mapper.asciidoc[] -Compared to writing mapping code from hand, MapStruct saves time by generating code which is tedious and error-prone to write. Following a convention over configuration approach, MapStruct uses sensible defaults but steps out of your way when it comes to configuring or implementing special behavior. +include::chapter-4-retrieving-a-mapper.asciidoc[] -Compared to dynamic mapping frameworks, MapStruct offers the following advantages: +include::chapter-5-data-type-conversions.asciidoc[] -* Fast execution by using plain method invocations instead of reflection -* Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no accidental mapping of an order entity into a customer DTO etc. -* Clear error-reports at build time, if entities or attributes can't be mapped +include::chapter-6-mapping-collections.asciidoc[] -[[setup]] -== Set up +include::chapter-7-mapping-streams.asciidoc[] -MapStruct is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. +include::chapter-8-mapping-values.asciidoc[] -It comprises the following artifacts: +include::chapter-9-object-factories.asciidoc[] -* _org.mapstruct:mapstruct_: contains the required annotations such as `@Mapping`; On Java 8 or later, use _org.mapstruct:mapstruct-jdk8_ instead which takes advantage of language improvements introduced in Java 8 -* _org.mapstruct:mapstruct-processor_: contains the annotation processor which generates mapper implementations +include::chapter-10-advanced-mapping-options.asciidoc[] -=== Apache Maven +include::chapter-11-reusing-mapping-configurations.asciidoc[] -For Maven based projects add the following to your POM file in order to use MapStruct: - -.Maven configuration -==== -[source, xml, linenums] -[subs="verbatim,attributes"] ----- -... - - {mapstructVersion} - -... - - - org.mapstruct - mapstruct-jdk8 - ${org.mapstruct.version} - - -... - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - - - - - -... ----- -==== - -[TIP] -==== -If you are working with the Eclipse IDE, make sure to have a current version of the http://www.eclipse.org/m2e/[M2E plug-in]. -When importing a Maven project configured as shown above, it will set up the MapStruct annotation processor so it runs right in the IDE, whenever you save a mapper type. -Neat, isn't it? - -To double check that everything is working as expected, go to your project's properties and select "Java Compiler" -> "Annotation Processing" -> "Factory Path". -The MapStruct processor JAR should be listed and enabled there. -Any processor options configured via the compiler plug-in (see below) should be listed under "Java Compiler" -> "Annotation Processing". - -If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled. -To do so, go to "Preferences" -> "Maven" -> "Annotation Processing" and select "Automatically configure JDT APT". -Alternatively, specify the following in the `properties` section of your POM file: `jdt_apt`. - -Also make sure that your project is using Java 1.6 or later (project properties -> "Java Compiler" -> "Compile Compliance Level"). -It will not work with older versions. -==== - -=== Gradle - -Add the following to your Gradle build file in order to enable MapStruct: - -.Gradle configuration -==== -[source, groovy, linenums] -[subs="verbatim,attributes"] ----- -... -plugins { - ... - id 'net.ltgt.apt' version '0.8' -} -dependencies { - ... - compile 'org.mapstruct:mapstruct-jdk8:{mapstructVersion}' - - apt 'org.mapstruct:mapstruct-processor:{mapstructVersion}' -} -... ----- -==== - -You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-gradle[mapstruct-examples] project on GitHub. - - -=== Apache Ant - -Add the `javac` task configured as follows to your _build.xml_ file in order to enable MapStruct in your Ant-based project. Adjust the paths as required for your project layout. - -.Ant configuration -==== -[source, xml, linenums] -[subs="verbatim,attributes"] ----- -... - - - - -... ----- -==== - -You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-ant[mapstruct-examples] project on GitHub. - -[[configuration-options]] -=== Configuration options - -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: - -.Maven configuration -==== -[source, xml, linenums] -[subs="verbatim,attributes"] ----- -... - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - - - - -Amapstruct.suppressGeneratorTimestamp=true - - - -Amapstruct.suppressGeneratorVersionInfoComment=true - - - - -... ----- -==== - -.Gradle configuration -==== -[source, groovy, linenums] -[subs="verbatim,attributes"] ----- -... -compileJava { - options.compilerArgs = [ - '-Amapstruct.suppressGeneratorTimestamp=true', - '-Amapstruct.suppressGeneratorVersionInfoComment=true' - ] -} -... ----- -==== - -The following options exist: - -.MapStruct processor options -[cols="1,2a,1"] -|=== -|Option|Purpose|Default - -|`mapstruct. -suppressGeneratorTimestamp` -|If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed. -|`false` - -|`mapstruct. -suppressGeneratorVersionInfoComment` -|If set to `true`, the creation of the `comment` attribute in the `@Generated` annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing. -|`false` - -|`mapstruct.defaultComponentModel` -|The name of the component model (see <>) based on which mappers should be generated. - -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` -* `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 - -If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence. -|`default` - -|`mapstruct.unmappedTargetPolicy` -|The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value. - -Supported values are: - -* `ERROR`: any unmapped target property will cause the mapping code generation to fail -* `WARN`: any unmapped target property will cause a warning at build time -* `IGNORE`: unmapped target properties are ignored - -If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence. -|`WARN` -|=== - -=== Using MapStruct on Java 9 - -MapStruct can be used with Java 9, but as that Java version has not been finalized yet, support for it is experimental. - -A core theme of Java 9 is the modularization of the JDK. One effect of this that a specific module need to be enabled for a project in order to use the `javax.annotation.Generated` annotation. `@Generated` is added by MapStruct to generated mapper classes to tag them as generated code, stating the date of generation, the generator version etc. - -To allow usage of the `@Generated` annotation the module _java.annotations.common_ must be enabled. When using Maven, this can be done like this: - - export MAVEN_OPTS="--add-modules java.annotations.common" - -If the `@Generated` annotation is not available, MapStruct will detect this situation and not add it to generated mappers. - -[[defining-mapper]] -== Defining a mapper - -In this section you'll learn how to define a bean mapper with MapStruct and which options you have to do so. - -[[basic-mappings]] -=== Basic mappings - -To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the `org.mapstruct.Mapper` annotation: - -.Maven configuration -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mappings({ - @Mapping(source = "make", target = "manufacturer"), - @Mapping(source = "numberOfSeats", target = "seatCount") - }) - CarDto carToCarDto(Car car); - - @Mapping(source = "name", target = "fullName") - PersonDto personToPersonDto(Person person); -} ----- -==== - -The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time. - -In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`). If a property has a different name in the target entity, its name can be specified via the `@Mapping` annotation. - -[TIP] -==== -The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans spefication] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`. -==== - -[TIP] -==== -When using Java 8 or later, you can omit the `@Mappings` wrapper annotation and directly specify several `@Mapping` annotations on one method. -==== - -To get a better understanding of what MapStruct does have a look at the following implementation of the `carToCarDto()` method as generated by MapStruct: - -.Code generated by MapStruct -==== -[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.getFeatures() != null ) { - carDto.setFeatures( new ArrayList( car.getFeatures() ) ); - } - carDto.setManufacturer( car.getMake() ); - carDto.setSeatCount( car.getNumberOfSeats() ); - carDto.setDriver( personToPersonDto( car.getDriver() ) ); - carDto.setPrice( String.valueOf( car.getPrice() ) ); - if ( car.getCategory() != null ) { - carDto.setCategory( car.getCategory().toString() ); - } - - return carDto; - } - - @Override - public PersonDto personToPersonDto(Person person) { - //... - } -} ----- -==== - -The general philosophy of MapStruct is to generate code which looks as much as possible as if you had written it yourself from hand. In particular this means that the values are copied from source to target by plain getter/setter invocations instead of reflection or similar. - -As the example shows the generated code takes into account any name mappings specified via `@Mapping`. If the type of a mapped attribute is different in source and target entity, MapStruct will either apply an automatic conversion (as e.g. for the _price_ property, see also <>) or optionally invoke another mapping method (as e.g. for the _driver_ property, see also <>). - -Collection-typed attributes with the same element type will be copied by creating a new instance of the target collection type containing the elements from the source property. For collection-typed attributes with different element types each element will be mapped individually and added to the target collection (see <>). - -MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types. - -[[adding-custom-methods]] -=== Adding custom methods to mappers - -In some cases it can be required to manually implement a specific mapping from one type to another which can't be generated by MapStruct. One way for this is to implement such method on another class which then is used by mappers generated by MapStruct (see <>). - -Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match. - -As an example let's assume the mapping from `Person` to `PersonDto` requires some special logic which can't be generated by MapStruct. You could then define the mapper from the previous example like this: - -.Mapper which defines a custom mapping with a default method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mappings({...}) - CarDto carToCarDto(Car car); - - default PersonDto personToPersonDto(Person person) { - //hand-written mapping logic - } -} ----- -==== - -The class generated by MapStruct implements the method `carToCarDto()`. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute. - -A mapper could also be defined in form of an abstract class instead of an interface and implement custom methods directly in this mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class. - -The previous example where the mapping from `Person` to `PersonDto` requires some special logic could then be defined like this: - -.Mapper defined by an abstract class -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public abstract class CarMapper { - - @Mappings(...) - public abstract CarDto carToCarDto(Car car); - - public PersonDto personToPersonDto(Person person) { - //hand-written mapping logic - } -} ----- -==== - -MapStruct will generate a sub-class of `CarMapper` with an implementation of the `carToCarDto()` method as it is declared abstract. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute. - -[[mappings-with-several-source-parameters]] -=== Mapping methods with several source parameters - -MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example: - -.Mapping method with several source parameters -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface AddressMapper { - - @Mappings({ - @Mapping(source = "person.description", target = "description"), - @Mapping(source = "address.houseNo", target = "houseNumber") - }) - DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address); -} ----- -==== - -The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name. - -In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically. - -[WARNING] -==== -Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation. -==== - -[TIP] -==== -Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated. -==== - -[[nested-mappings]] -=== Nested mappings - -MapStruct will handle nested mappings (in source), by means of the `.` notation: - -.Mapping method with several source parameters -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mappings({ - @Mapping(target = "chartName", source = "chart.name"), - @Mapping(target = "title", source = "song.title"), - @Mapping(target = "artistName", source = "song.artist.name"), - @Mapping(target = "recordedAt", source = "song.artist.label.studio.name"), - @Mapping(target = "city", source = "song.artist.label.studio.city"), - @Mapping(target = "position", source = "position") -}) -ChartEntry map(Chart chart, Song song, Integer position); ----- -==== - -Note: the parameter name (`chart`, `song`, `position`) is required, since there are several source parameters in the mapping. If there's only one source parameter, the parameter name can be ommited. - -MapStruct will perform a null check on each nested property in the source. - -[TIP] -==== -Also non java bean source parameters (like the `java.lang.Integer`) can be mapped in this fashion. -==== - -[[updating-bean-instances]] -=== Updating existing bean instances - -In some cases you need mappings which don't create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with `@MappingTarget`. The following shows an example: - -.Update method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - void updateCarFromDto(CarDto carDto, @MappingTarget Car car); -} ----- -==== - -The generated code of the `updateCarFromDto()` method will update the passed `Car` instance with the properties from the given `CarDto` object. There may be only one parameter marked as mapping target. Instead of `void` you may also set the method's return type to the type of the target parameter, which will cause the generated implementation to update the passed mapping target and return it as well. This allows for fluent invocations of mapping methods. - -Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. - -[[direct-field-mappings]] -=== Mappings with direct field access - -MapStruct also supports mappings of `public` fields that have no getters/setters. MapStruct will -use the fields as read/write accessor if it cannot find suitable getter/setter methods for the property. - -A field is considered as a read accessor if it is `public` or `public final`. If a field is `static` it is not -considered as a read accessor. - -A field is considered as a write accessor only if it is `public`. If a field is `final` and/or `static` it is not -considered as a write accessor. - -Small example: - -.Examples classes for mapping -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class Customer { - - private Long id; - private String name; - - //getters and setter omitted for brevity -} - -public class CustomerDto { - - public Long id; - public String customerName; -} - -@Mapper -public interface CustomerMapper { - - CustomerMapper MAPPER = Mappers.getMapper( CustomerMapper.class ); - - @Mapping(source = "customerName", target = "name") - Customer toCustomer(CustomerDto customerDto); - - @InheritInverseConfiguration - CustomerDto fromCustomer(Customer customer); -} ----- -==== - -For the configuration from above, the generated mapper looks like: - -.Generated mapper for example classes -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class CustomerMapperImpl implements CustomerMapper { - - @Override - public Customer toCustomer(CustomerDto customerDto) { - // ... - customer.setId( customerDto.id ); - customer.setName( customerDto.customerName ); - // ... - } - - @Override - public CustomerDto fromCustomer(Customer customer) { - // ... - customerDto.id = customer.getId(); - customerDto.customerName = customer.getName(); - // ... - } -} ----- -==== - -You can find the complete example in the -https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping] -project on GitHub. - -[[retrieving-mapper]] -== Retrieving a mapper - -[[mappers-factory]] -=== The Mappers factory - -Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return: - -.Using the Mappers factory -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -CarMapper mapper = Mappers.getMapper( CarMapper.class ); ----- -==== - -By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type: - -.Declaring an instance of a mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); - - CarDto carToCarDto(Car car); -} - ----- -==== - -This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances: - -.Accessing a mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -Car car = ...; -CarDto dto = CarMapper.INSTANCE.carToCarDto( car ); ----- -==== - -Note that mappers generated by MapStruct are thread-safe and thus can safely be accessed from several threads at the same time. - -[[using-dependency-injection]] -=== Using dependency injection - -If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection as well. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <>. - -Currently there is support for CDI and Spring (the later either via its custom annotations or using the JSR 330 annotations). See <> for the allowed values of the `componentModel` attribute which are the same as for the `mapstruct.defaultComponentModel` processor option. In both cases the required annotations will be added to the generated mapper implementations classes in order to make the same subject to dependency injection. The following shows an example using CDI: - -.A mapper using the CDI component model -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(componentModel = "cdi") -public interface CarMapper { - - CarDto carToCarDto(Car car); -} - ----- -==== - -The generated mapper implementation will be marked with the `@ApplicationScoped` annotation and thus can be injected into fields, constructor arguments etc. using the `@Inject` annotation: - -.Obtaining a mapper via dependency injection -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Inject -private CarMapper mapper; ----- -==== - -A mapper which uses other mapper classes (see <>) will obtain these mappers using the configured component model. So if `CarMapper` from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well. - -[[datatype-conversions]] -== Data type conversions - -Not always a mapped attribute has the same type in the source and target objects. For instance an attribute may be of type `int` in the source bean but of type `Long` in the target bean. - -Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class `Car` might have a property `driver` of the type `Person` which needs to be converted into a `PersonDto` object when mapping a `Car` object. - -In this section you'll learn how MapStruct deals with such data type conversions. - -[[implicit-type-conversions]] -=== Implicit type conversions - -MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of type `int` in the source bean but of type `String` in the target bean, the generated code will transparently perform a conversion by calling `String#valueOf(int)` and `Integer#parseInt(String)`, respectively. - -Currently the following conversions are applied automatically: - -* Between all Java primitive data types and their corresponding wrapper types, e.g. between `int` and `Integer`, `boolean` and `Boolean` etc. The generated code is `null` aware, i.e. when converting a wrapper type into the corresponding primitive type a `null` check will be performed. - -* Between all Java primitive number types and the wrapper types, e.g. between `int` and `long` or `byte` and `Integer`. - -[WARNING] -==== -Converting from larger data types to smaller ones (e.g. from `long` to `int`) can cause a value or precision loss. There https://github.com/mapstruct/mapstruct/issues/5[will be] an option for raising a warning in such cases in a future MapStruct version. -==== - -* Between all Java primitive types (including their wrappers) and `String`, e.g. between `int` and `String` or `Boolean` and `String`. A format string as understood by `java.text.DecimalFormat` can be specified. - -.Conversion from int to String -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "price", numberFormat = "$#.00") - CarDto carToCarDto(Car car); - - @IterableMapping(numberFormat = "$#.00") - List prices(List prices); -} ----- -==== -* Between `enum` types and `String`. - -* 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 -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "power", numberFormat = "#.##E0") - CarDto carToCarDto(Car car); - -} ----- -==== - - -* Between `JAXBElement` and `T`, `List>` and `List` - -* Between `java.util.Calendar`/`java.util.Date` and JAXB's `XMLGregorianCalendar` - -* Between `java.util.Date`/`XMLGregorianCalendar` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option as this: - -.Conversion from Date to String -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy") - CarDto carToCarDto(Car car); - - @IterableMapping(dateFormat = "dd.MM.yyyy") - List stringListToDateList(List dates); -} ----- -==== - -* Between Jodas `org.joda.time.DateTime`, `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate`, `org.joda.time.LocalTime` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). - -* Between Jodas `org.joda.time.DateTime` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Calendar`. - -* Between Jodas `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Date`. - -* Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). - -* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Date` where, when mapping a `ZonedDateTime` from a given `Date`, the system default timezone is used. - -* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone. - -* Between `java.time.LocalDate` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone. - -* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`. - -* Between `java.sql.Date` and `java.util.Date` - -* Between `java.sql.Time` and `java.util.Date` - -* Between `java.sql.Timestamp` and `java.util.Date` - -* When converting from a `String`, omitting `Mapping#dateFormat`, it leads to usage of the default pattern and date format symbols for the default locale. An exception to this rule is `XmlGregorianCalendar` which results in parsing the `String` according to http://www.w3.org/TR/xmlschema-2/#dateTime[XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation]. - -[[mapping-object-references]] -=== Mapping object references - -Typically an object has not only primitive attributes but also references other objects. E.g. the `Car` class could contain a reference to a `Person` object (representing the car's driver) which should be mapped to a `PersonDto` object referenced by the `CarDto` class. - -In this case just define a mapping method for the referenced object type as well: - -.Mapper with one mapping method using another -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - CarDto carToCarDto(Car car); - - PersonDto personToPersonDto(Person person); -} ----- -==== - -The generated code for the `carToCarDto()` method will invoke the `personToPersonDto()` method for mapping the `driver` attribute, while the generated implementation for `personToPersonDto()` performs the mapping of person objects. - -That way it is possible to map arbitrary deep object graphs. When mapping from entities into data transfer objects it is often useful to cut references to other entities at a certain point. To do so, implement a custom mapping method (see the next section) which e.g. maps a referenced entity to its id in the target object. - -When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object: - -* If source and target attribute have the same type, the value will be simply copied from source to target. If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target attribute. -* If source and target attribute type differ, check whether there is a another mapping method which has the type of the source attribute as parameter type and the type of the target attribute as return type. If such a method exists it will be invoked in the generated mapping implementation. -* If no such method exists MapStruct will look whether a built-in conversion for the source and target type of the attribute exists. If this is the case, the generated mapping code will apply this conversion. -* Otherwise an error will be raised at build time, indicating the non-mappable attribute. - -[[invoking-other-mappers]] -=== Invoking other mappers - -In addition to methods defined on the same mapper type MapStruct can also invoke mapping methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping methods. This can be useful to structure your mapping code in several classes (e.g. with one mapper type per application module) or if you want to provide custom mapping logic which can't be generated by MapStruct. - -For instance the `Car` class might contain an attribute `manufacturingDate` while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this: - -.Manually implemented mapper class -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class DateMapper { - - public String asString(Date date) { - return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) - .format( date ) : null; - } - - public Date asDate(String date) { - try { - return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) - .parse( date ) : null; - } - catch ( ParseException e ) { - throw new RuntimeException( e ); - } - } -} ----- -==== - -In the `@Mapper` annotation at the `CarMapper` interface reference the `DateMapper` class like this: - -.Referencing another mapper class -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses=DateMapper.class) -public class CarMapper { - - CarDto carToCarDto(Car car); -} ----- -==== - -When generating code for the implementation of the `carToCarDto()` method, MapStruct will look for a method which maps a `Date` object into a String, find it on the `DateMapper` class and generate an invocation of `asString()` for mapping the `manufacturingDate` attribute. - -Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model for `CarMapper`, `DateMapper` would have to be a CDI bean as well. When using the default component model, any hand-written mapper classes to be referenced by MapStruct generated mappers must declare a public no-args constructor in order to be instantiable. - -[[passing-target-type]] -=== Passing the mapping target type to custom mappers - -When having a custom mapper hooked into the generated mapper with `@Mapper#uses()`, an additional parameter of type `Class` (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with `@TargetType` for MapStruct to generate calls that pass the `Class` instance representing the corresponding property type of the target bean. - -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. - -.Mapping method expecting mapping target type as parameter -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@ApplicationScoped // CDI component model -public class ReferenceMapper { - - @PersistenceContext - private EntityManager entityManager; - - public T resolve(Reference reference, @TargetType Class entityClass) { - return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null; - } - - public Reference toReference(BaseEntity entity) { - return entity != null ? new Reference( entity.getPk() ) : null; - } -} - -@Mapper(componentModel = "cdi", uses = ReferenceMapper.class ) -public interface CarMapper { - - Car carDtoToCar(CarDto carDto); -} ----- -==== - -MapStruct will then generate something like this: - -.Generated code -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@ApplicationScoped -public class CarMapperImpl implements CarMapper { - - @Inject - private ReferenceMapper referenceMapper; - - @Override - public Car carDtoToCar(CarDto carDto) { - if ( carDto == null ) { - return null; - } - - Car car = new Car(); - - car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) ); - // ... - - return car; - } -} ----- -==== - -[[mapping-method-resolution]] -=== Mapping method resolution - -When mapping a property from one type to another, MapStruct looks for the most specific method which maps the source type into the target type. The method may either be declared on the same mapper interface or on another mapper which is registered via `@Mapper#uses()`. The same applies for factory methods (see <>). - -The algorithm for finding a mapping or factory method resembles Java's method resolution algorithm as much as possible. In particular, methods with a more specific source type will take precedence (e.g. if there are two methods, one which maps the searched source type, and another one which maps a super-type of the same). In case more than one most-specific method is found, an error will be raised. - -[TIP] -==== -When working with JAXB, e.g. when converting a `String` to a corresponding `JAXBElement`, MapStruct will take the `scope` and `name` attributes of `@XmlElementDecl` annotations into account when looking for a mapping method. This makes sure that the created `JAXBElement` instances will have the right QNAME value. You can find a test which maps JAXB objects https://github.com/mapstruct/mapstruct/blob/{mapstructVersion}/integrationtest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java[here]. -==== - -[[selection-based-on-qualifiers]] -=== Mapping method selection based on qualifiers - -In many occasions one requires mapping methods with the same method signature (appart from the name) that have different behavior. MapStruct has a handy mechanism to deal with such situations: `@Qualifier`. A ‘qualifier’ is a custom annotation that the user can write, ‘stick onto’ a mapping method which is included as used mapper, and can be referred to in a bean property mapping, iterable mapping or map mapping. Multiple qualifiers can be ‘stuck onto’ a method and mapping. - -So, lets say there is a hand-written method to map titles with a `String` return type and `String` argument amongst many other referenced mappers with the same `String` return type - `String` argument signature: - -.Several mapping methods with identical source and target types -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class Titles { - - public String translateTitleEG(String title) { - // some mapping logic - } - - public String translateTitleGE(String title) { - // some mapping logic - } -} ----- -==== - -And a mapper using this handwritten mapper, in which source and target have a property 'title' that should be mapped: - -.Mapper causing an ambiguous mapping method error -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = Titles.class ) -public interface MovieMapper { - - GermanRelease toGerman( OriginalRelease movies ); - -} ----- -==== - -Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2 qualifying methods are found (`translateTitleEG`, `translateTitleGE`) and MapStruct would not have a hint which one to choose. - -Enter the qualifier approach: - -.Declaring a qualifier type -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Qualifier -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.CLASS) -public @interface TitleTranslator { -} ----- -==== - -And, some qualifiers to indicate which translator to use to map from source language to target language: - -.Declaring qualifier types for mapping methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Qualifier -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface EnglishToGerman { -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Qualifier -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.CLASS) -public @interface GermanToEnglish { -} ----- -==== - -Please take note of the retention `TitleTranslator` on class level, `EnglishToGerman`, `GermanToEnglish` on method level! - -Then, using the qualifiers, the mapping could look like this: - -.Mapper using qualifiers -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = Titles.class ) -public interface MovieMapper { - - @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } ) - GermanRelease toGerman( OriginalRelease movies ); - -} ----- -==== - -.Custom mapper qualifying the methods it provides -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@TitleTranslator -public class Titles { - - @EnglishToGerman - public String translateTitleEG(String title) { - // some mapping logic - } - - @GermanToEnglish - public String translateTitleGE(String title) { - // some mapping logic - } -} ----- -==== - -[WARNING] -==== -Please make sure the used retention policy equals retention policy `CLASS` (`@Retention(RetentionPolicy.CLASS)`). -==== - -[WARNING] -==== -A class / method annotated with a qualifier will not qualify anymore for mappings that do not have the `qualifiedBy` element. -==== - -[TIP] -==== -The same mechanism is also present on bean mappings: `@BeanMapping#qualifiedBy`: it selects the factory method marked with the indicated qualifier. -==== - -In many occasions, declaring a new annotation to aid the selection process can be too much for what you try to achieve. For those situations, MapStruct has the `@Named` annotation. This annotation is a pre-defined qualifier (annotated with `@Qualifier` itself) and can be used to name a Mapper or, more directly a mapping method by means of its value. The same example above would look like: - -.Custom mapper, annotating the methods to qualify by means of `@Named` -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Named("TitleTranslator") -public class Titles { - - @Named("EnglishToGerman") - public String translateTitleEG(String title) { - // some mapping logic - } - - @Named("GermanToEnglish") - public String translateTitleGE(String title) { - // some mapping logic - } -} ----- -==== - -.Mapper using named -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = Titles.class ) -public interface MovieMapper { - - @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } ) - GermanRelease toGerman( OriginalRelease movies ); - -} ----- -==== - -[WARNING] -==== -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. -==== - - -[[mapping-collections]] -== Mapping collections - -The mapping of collection types (`List`, `Set` etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface. MapStruct supports a wide range of iterable types from the http://docs.oracle.com/javase/tutorial/collections/intro/index.html[Java Collection Framework]. - -The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection. If a mapping method for the collection element types is found in the given mapper or the mapper it uses, this method is invoked to perform the element conversion. Alternatively, if an implicit conversion for the source and target element types exists, this conversion routine will be invoked. The following shows an example: - -.Mapper with collection mapping methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - Set integerSetToStringSet(Set integers); - - List carsToCarDtos(List cars); - - CarDto carToCarDto(Car car); -} ----- -==== - -The generated implementation of the `integerSetToStringSet` performs the conversion from `Integer` to `String` for each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained element as shown in the following: - -.Generated collection mapping methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@Override -public Set integerSetToStringSet(Set integers) { - if ( integers == null ) { - return null; - } - - Set set = new HashSet(); - - for ( Integer integer : integers ) { - set.add( String.valueOf( integer ) ); - } - - return set; -} - -@Override -public List carsToCarDtos(List cars) { - if ( cars == null ) { - return null; - } - - List list = new ArrayList(); - - for ( Car car : cars ) { - list.add( carToCarDto( car ) ); - } - - return list; -} ----- -==== - -Note that MapStruct will look for a collection mapping method with matching parameter and return type, when mapping a collection-typed attribute of a bean, e.g. from `Car#passengers` (of type `List`) to `CarDto#passengers` (of type `List`). - -.Usage of collection mapping method to map a bean property -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) ); -... ----- -==== - -Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In this case the generated code for mapping such a property invokes its getter and adds all the mapped elements: - -.Usage of an adding method for collection mapping -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) ); -... ----- -==== - -[WARNING] -==== -It is not allowed to declare mapping methods with an iterable source and a non-iterable target or the other way around. An error will be raised when detecting this situation. -==== - -[[mapping-maps]] -=== Mapping maps - -Also map-based mapping methods are supported. The following shows an example: - -.Map mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public interface SourceTargetMapper { - - @MapMapping(valueDateFormat = "dd.MM.yyyy") - Map longDateMapToStringStringMap(Map source); -} ----- -==== - -Similar to iterable mappings, the generated code will iterate through the source map, convert each value and key (either by means of an implicit conversion or by invoking another mapping method) and put them into the target map: - -.Generated implementation of map mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@Override -public Map stringStringMapToLongDateMap(Map source) { - if ( source == null ) { - return null; - } - - Map map = new HashMap(); - - for ( Map.Entry entry : source.entrySet() ) { - - Long key = Long.parseLong( entry.getKey() ); - Date value; - try { - value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() ); - } - catch( ParseException e ) { - throw new RuntimeException( e ); - } - - map.put( key, value ); - } - - return map; -} ----- -==== - -[[collection-mapping-strategies]] -=== Collection mapping strategies - -MapStruct has a `CollectionMappingStrategy`, with the possible values: `ACCESSOR_ONLY`, `SETTER_PREFERRED` and `ADDER_PREFERRED`. - -In the table below, the dash `-` indicates a property name. Next, the trailing `s` indicates the plural form. The table explains the options and how they are apply to the presence/absense of a `set-s`, `add-` and / or `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(`@TargetType`) - -|`ACCESSOR_ONLY` -|set-s -|get-s -|set-s -|get-s -|get-s - -|`SETTER_PREFERRED` -|set-s -|add- -|set-s -|get-s -|get-s - -|`ADDER_PREFERRED` -|set-s -|add- -|add- -|get-s -|get-s -|=== - -Some background: An `adder` method is typically used in case of http://www.eclipse.org/webtools/dali/[generated (JPA) entities], to add a single element (entity) to an underlying collection. Invoking the adder establishes a parent-child relation between parent - the bean (entity) on which the adder is invoked - and its child(ren), the elements (entities) in the collection. To find the appropriate `adder`, MapStruct will try to make a match between the generic parameter type of the underlying collection and the single argument of a candidate `adder`. When there are more candidates, the plural `setter` / `getter` name is converted to singular and will be used in addition to make a match. - -The option `DEFAULT` should not be used explicitely. It is used to distinguish between an explicit user desire to override the default in a `@MapperConfig` from the implicit Mapstruct choice in a `@Mapper`. The option `DEFAULT` is synonymous to `ACCESSOR_ONLY`. - -[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 in stead of Mapstruct creating the target entity by its constructor. -==== - -[[implementation-types-for-collection-mappings]] -=== Implementation types used for collection mappings - -When an iterable or map mapping method declares an interface type as return type, one of its implementation types will be instantiated in the generated code. The following table shows the supported interface types and their corresponding implementation types as instantiated in the generated code: - -.Collection mapping implementation types -|=== -|Interface type|Implementation type - -|`Iterable`|`ArrayList` - -|`Collection`|`ArrayList` - -|`List`|`ArrayList` - -|`Set`|`HashSet` - -|`SortedSet`|`TreeSet` - -|`NavigableSet`|`TreeSet` - -|`Map`|`HashMap` - -|`SortedMap`|`TreeMap` - -|`NavigableMap`|`TreeMap` - -|`ConcurrentMap`|`ConcurrentHashMap` -|`ConcurrentNavigableMap`|`ConcurrentSkipListMap` -|=== - -[[mapping-enum-types]] -== Mapping Values - -=== Mapping enum types - -MapStruct supports the generation of methods which map one Java enum type into another. - -By default, each constant from the source enum is mapped to a constant with the same name in the target enum type. If required, a constant from the source enum may be mapped to a constant with another name with help of the `@ValueMapping` annotation. Several constants from the source enum can be mapped to the same constant in the target type. - -The following shows an example: - -.Enum mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface OrderMapper { - - OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); - - @ValueMappings({ - @ValueMapping(source = "EXTRA", target = "SPECIAL"), - @ValueMapping(source = "STANDARD", target = "DEFAULT"), - @ValueMapping(source = "NORMAL", target = "DEFAULT") - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} ----- -==== - -.Enum mapping method result -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class OrderMapperImpl implements OrderMapper { - - @Override - public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { - if ( orderType == null ) { - return null; - } - - ExternalOrderType externalOrderType_; - - switch ( orderType ) { - case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL; - break; - case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT; - break; - case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT; - break; - case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; - break; - case B2B: externalOrderType_ = ExternalOrderType.B2B; - break; - default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType ); - } - - return externalOrderType_; - } -} ----- -==== -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. - -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. It comes in two flavors: `` and ``. - -In case of source `` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `` source. - -MapStruct will *not* attempt such name based mapping for `` and directly apply the target specified in the `@ValueMapping` with `` source to the remainder. - -MapStruct is able to handle `null` sources and `null` targets by means of the `` keyword. - -[TIP] -==== -Constants for ``, `` and `` are available in the `MappingConstants` class. -==== - -Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. - -.Enum mapping method, and -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface SpecialOrderMapper { - - SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class ); - - @ValueMappings({ - @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ), - @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ), - @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" ) - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} ----- -==== - -.Enum mapping method result, and -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class SpecialOrderMapperImpl implements SpecialOrderMapper { - - @Override - public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { - if ( orderType == null ) { - return ExternalOrderType.DEFAULT; - } - - ExternalOrderType externalOrderType_; - - switch ( orderType ) { - case STANDARD: externalOrderType_ = null; - break; - case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; - break; - case B2B: externalOrderType_ = ExternalOrderType.B2B; - break; - default: externalOrderType_ = ExternalOrderType.SPECIAL; - } - - return externalOrderType_; - } -} ----- -==== - -*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``. - - -[WARNING] -==== -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. -==== - - -[[object-factories]] -== Object factories - -By default, the generated code for mapping one bean type into another will call the default constructor to instantiate the target type. - -Alternatively you can plug in custom object factories which will be invoked to obtain instances of the target type. One use case for this is JAXB which creates `ObjectFactory` classes for obtaining new instances of schema types. - -To make use of custom factories register them via `@Mapper#uses()` as described in <>. When creating the target object of a bean mapping, MapStruct will look for a parameterless method, -a method annotated with `@ObjectFactory`, or a method with only one `@TargetType` parameter that returns the required target type and invoke this method instead of calling the default constructor: - -.Custom object factories -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class DtoFactory { - - public CarDto createCarDto() { - return // ... custom factory logic - } -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class EntityFactory { - - public T createEntity(@TargetType Class entityClass) { - return // ... custom factory logic - } -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses= { DtoFactory.class, EntityFactory.class } ) -public interface CarMapper { - - OrderMapper INSTANCE = Mappers.getMapper( CarMapper.class ); - - CarDto carToCarDto(Car car); - - Car carDtoToCar(CarDto carDto); -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -public class CarMapperImpl implements CarMapper { - - private final DtoFactory dtoFactory = new DtoFactory(); - - private final EntityFactory entityFactory = new EntityFactory(); - - @Override - public CarDto carToCarDto(Car car) { - if ( car == null ) { - return null; - } - - CarDto carDto = dtoFactory.createCarDto(); - - //map properties... - - return carDto; - } - - @Override - public Car carDtoToCar(CarDto carDto) { - if ( carDto == null ) { - return null; - } - - Car car = entityFactory.createEntity( Car.class ); - - //map properties... - - return car; - } -} ----- -==== - -In addition, annotating a factory with `@ObjectFactory` lets you gain access to the mapping sources. -Source objects can be added as parameters in the same way as for mapping method. The `@ObjectFactory` -annotation is necessary to let MapStruct know that the given method is only a factory method. - -.Custom object factories with `@ObjectFactory` - -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class DtoFactory { - - @ObjectFactory - public CarDto createCarDto(Car car) { - return // ... custom factory logic - } -} ----- -==== - - -== Advanced mapping options -This chapter describes several advanced options which allow to fine-tune the behavior of the generated mapping code as needed. - -[[default-values-and-constants]] -=== Default values and constants - -Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values and are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property. - -A mapping with a constant must not include a reference to a source property. The following examples shows some mappings using default values and constants: - -.Mapping method with default values and constants -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses = StringListMapper.class) -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - @Mappings( { - @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined"), - @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1"), - @Mapping(target = "stringConstant", constant = "Constant Value"), - @Mapping(target = "integerConstant", constant = "14"), - @Mapping(target = "longWrapperConstant", constant = "3001"), - @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), - @Mapping(target = "stringListConstants", constant = "jack-jill-tom") - } ) - Target sourceToTarget(Source s); -} ----- -==== - -If `s.getStringProp() == null`, then the target property `stringProperty` will be set to `"undefined"` instead of applying the value from `s.getStringProp()`. If `s.getLongProperty() == null`, then the target property `longProperty` will be set to `-1`. -The String `"Constant Value"` is set as is to the target property `stringConstant`. The value `"3001"` is type-converted to the `Long` (wrapper) class of target property `longWrapperConstant`. Date properties also require a date format. The constant `"jack-jill-tom"` demonstrates how the hand-written class `StringListMapper` is invoked to map the dash-separated list into a `List`. - -[[expressions]] -=== Expressions - -By means of Expressions it will be possible to include constructs from a number of languages. - -Currently only Java is supported as language. This feature is e.g. useful to invoke constructors. The entire source object is available for usage in the expression. Care should be taken to insert only valid Java code: MapStruct will not validate the expression at generation-time, but errors will show up in the generated classes during compilation. - -The example below demonstrates how two source properties can be mapped to one target: - -.Mapping method using an expression -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - @Mapping(target = "timeAndFormat", - expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )") - Target sourceToTarget(Source s); -} ----- -==== - -The example demonstrates how the source properties `time` and `format` are composed into one target property `TimeAndFormat`. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `TimeAndFormat` class (unless its used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining `imports` on the `@Mapper` annotation. - -.Declaring an import -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -imports org.sample.TimeAndFormat; - -@Mapper( imports = TimeAndFormat.class ) -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - @Mapping(target = "timeAndFormat", - expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )") - Target sourceToTarget(Source s); -} ----- -==== - -[[determining-result-type]] -=== Determining the result type - -When result types have an inheritance relation, selecting either mapping method (`@Mapping`) or a factory method (`@BeanMapping`) can becomes ambigious. Suppose an Apple and a Banana, which is are both specializations of Fruit. - -.Specifying the result type of a bean mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = FruitFactory.class ) -public interface FruitMapper { - - @BeanMapping( resultType = Apple.class ) - Fruit map( FruitDto source ); - -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class FruitFactory { - - public Apple createApple() { - return new Apple( "Apple" ); - } - - public Banana createBanana() { - return new Banana( "Banana" ); - } -} ----- -==== - -So, which `Fruit` must be factorized in the mapping method `Fruit map(FruitDto source);`? A `Banana` or an `Apple`? Here's were the `@BeanMapping#resultType` comes in handy. It controls the factory method to select, or in absence of a factory method, the return type to create. - -[TIP] -==== -The same mechanism is present on mapping: `@Mapping#resultType` and works like you expect it would: it selects the mapping method with the desired result type when present. -==== - -[TIP] -==== -The mechanism is also present on iterable mapping and map mapping. `@IterableMapping#elementTargetType` is used to select the mapping method with the desired element in the resulting `Iterable`. For the `@MapMapping` a similar purpose is served by means of `#MapMapping#keyTargetType` and `MapMapping#valueTargetType`. -==== - -[[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. - -However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MappingConfig`, the mapping result can be altered to return empty *default* values. This means for: - -* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present. -* *Primitives*: the default values for primitives will be returned, e.g. `false` for `boolean` or `0` for `int`. -* *Iterables / Arrays*: an empty iterable will be returned. -* *Maps*: an empty map will be returned. - -The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`. - - -[[checking-source-property-for-null-arguments]] -=== Controlling checking result for 'null' properties in bean mapping - -MapStruct offers control over when to generate a `null` check. By default (`nullValueCheckStrategy = NullValueMappingStrategy.ON_IMPLICIT_CONVERSION`) a `null` check will be generated for: - -* direct setting of source value to target value when target is primitive and is source not. -* applying type conversion and then: -.. calling the setter on the target. -.. calling another type conversion and subsequently calling the setter on the target. -.. calling a mapping method and subsequently calling the setter on the target. - -First calling a mapping method on the source property is not protected by a null check. Therefor generated mapping methods will do a null check prior to carrying out mapping on a source property. Handwritten mapping methods must take care of null value checking. They have the possibility to add 'meaning' to `null`. For instance: mapping `null` to a default value. - -The option `nullValueCheckStrategy = NullValueMappingStrategy.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. `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`. - -[[source-presence-check]] -=== Source presence checking -Some frameworks generate bean properties that have a source presence checker. Often this is in the form of a method `hasXYZ`, `XYZ` being a property on the source bean in a bean mapping method. MapStruct will call this `hasXYZ` instead of performing a `null` check when it finds such `hasXYZ` method. - -[TIP] -==== -The source presence checker name can be changed in the MapStruct service provider interface (SPI). It can also be deactivated in this way. -==== - -[[exceptions]] -=== Exceptions - -Calling applications may require handling of exceptions when calling a mapping method. These exceptions could be thrown by hand-written logic and by the generated built-in mapping methods or type-conversions of MapStruct. When the calling application requires handling of exceptions, a throws clause can be defined in the mapping method: - -.Mapper using custom method declaring checked exception -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses = HandWritten.class) -public interface CarMapper { - - CarDto carToCarDto(Car car) throws GearException; -} ----- -==== - -The hand written logic might look like this: - -.Custom mapping method declaring checked exception -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class HandWritten { - - private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"}; - - public String toGear(Integer gear) throws GearException, FatalException { - if ( gear == null ) { - throw new FatalException("null is not a valid gear"); - } - - if ( gear < 0 && gear > GEAR.length ) { - throw new GearException("invalid gear"); - } - return GEAR[gear]; - } -} ----- -==== - -MapStruct now, wraps the `FatalException` in a `try-catch` block and rethrows an unchecked `RuntimeException`. MapStruct delegates handling of the `GearException` to the application logic because it is defined as throws clause in the `carToCarDto` method: - -.try-catch block in generated implementation -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -@Override -public CarDto carToCarDto(Car car) throws GearException { - if ( car == null ) { - return null; - } - - CarDto carDto = new CarDto(); - try { - carDto.setGear( handWritten.toGear( car.getGear() ) ); - } - catch ( FatalException e ) { - throw new RuntimeException( e ); - } - - return carDto; -} ----- -==== - -Some **notes** on null checks. MapStruct does provide null checking only when required: when applying type-conversions or constructing a new type by invoking its constructor. This means that the user is responsible in hand-written code for returning valid non-null objects. Also null objects can be handed to hand-written code, since MapStruct does not want to make assumptions on the meaning assigned by the user to a null object. Hand-written code has to deal with this. - -== Reusing mapping configurations - -This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types. - -[[mapping-configuration-inheritance]] -=== Mapping configuration inheritance - -Method-level configuration annotations such as `@Mapping`, `@BeanMapping`, `@IterableMapping`, etc., can be *inherited* from one mapping method to a *similar* method using the annotation `@InheritConfiguration`: - -.Update method inheriting its configuration -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(target = "numberOfSeats", source = "seatCount") - Car carDtoToCar(CarDto car); - - @InheritConfiguration - void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); -} ----- -==== - -The example above declares a mapping method `carDtoToCar()` with a configuration to define how the property `numberOfSeats` in the type `Car` shall be mapped. The update method that performs the mapping on an existing instance of `Car` needs the same configuration to successfully map all properties. Declaring `@InheritConfiguration` on the method lets MapStruct search for inheritance candidates to apply the annotations of the method that is inherited from. - -One method *A* can inherit the configuration from another method *B* if all types of *A* (source types and result type) are assignable to the corresponding types of *B*. - -Methods that are considered for inheritance need to be defined in the current mapper, a super class/interface, or in the shared configuration interface (as described in <>). - -In case more than one method is applicable as source for the inheritance, the method name must be specified within the annotation: `@InheritConfiguration( name = "carDtoToCar" )`. - -A method can use `@InheritConfiguration` and override or amend the configuration by additionally applying `@Mapping`, `@BeanMapping`, etc. - -[[inverse-mappings]] -=== Inverse mappings - -In case of bi-directional mappings, e.g. from entity to DTO and from DTO to entity, the mapping rules for the forward method and the reverse method are often similar and can simply be inversed by switching `source` and `target`. - -Use the annotation `@InheritInverseConfiguration` to indicate that a method shall inherit the inverse configuration of the corresponding reverse method. - -.Inverse mapping method inheriting its configuration -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "numberOfSeats", target = "seatCount") - CarDto carToDto(Car car); - - @InheritInverseConfiguration - Car carDtoToCar(CarDto carDto); -} ----- -==== - -Here the `carDtoToCar()` method is the reverse mapping method for `carToDto()`. Note that any attribute mappings from `carToDto()` will be applied to the corresponding reverse mapping method as well. They are automatically reversed and copied to the method with the `@InheritInverseConfiguration` annotation. - -Specific mappings from the inversed method can (optionally) be overridden by `ignore`, `expression` or `constant` in the mapping, e.g. like this: `@Mapping(target = "numberOfSeats", ignore=true)`. - -A method *A* is considered a *reverse* method of a method *B*, if the result type of *A* is the *same* as the single source type of *B* and if the single source type of *A* is the *same* as the result type of *B*. - -Methods that are considered for inverse inheritance need to be defined in the current mapper, a super class/interface. - -If multiple methods qualify, the method from which to inherit the configuration needs to be specified using the `name` property like this: `@InheritInverseConfiguration(name = "carToDto")`. - -Expressions and constants are excluded (silently ignored). Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping. - -[[shared-configurations]] -=== Shared configurations - -MapStruct offers the possibility to define a shared configuration by pointing to a central interface annotated with `@MapperConfig`. For a mapper to use the shared configuration, the configuration interface needs to be defined in the `@Mapper#config` property. - -The `@MapperConfig` annotation has the same attributes as the `@Mapper` annotation. Any attributes not given via `@Mapper` will be inherited from the shared configuration. Attributes specified in `@Mapper` take precedence over the attributes specified via the referenced configuration class. List properties such as `uses` are simply combined: - -.Mapper configuration class and mapper using it -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@MapperConfig( - uses = CustomMapperViaMapperConfig.class, - unmappedTargetPolicy = ReportingPolicy.ERROR -) -public interface CentralConfig { -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) -// Effective configuration: -// @Mapper( -// uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class }, -// unmappedTargetPolicy = ReportingPolicy.ERROR -// ) -public interface SourceTargetMapper { - ... -} - ----- -==== - -The interface holding the `@MapperConfig` annotation may also declare *prototypes* of mapping methods that can be used to inherit method-level mapping annotations from. Such prototype methods are not meant to be implemented or used as part of the mapper API. - -.Mapper configuration class with prototype methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@MapperConfig( - uses = CustomMapperViaMapperConfig.class, - unmappedTargetPolicy = ReportingPolicy.ERROR, - mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG -) -public interface CentralConfig { - - // Not intended to be generated, but to carry inheritable mapping annotations: - @Mapping(target = "primaryKey", source = "technicalKey") - BaseEntity anyDtoToEntity(BaseDto dto); -} ----- -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) -public interface SourceTargetMapper { - - @Mapping(target = "numberOfSeats", source = "seatCount") - // additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto: - // @Mapping(target = "primaryKey", source = "technicalKey") - Car toCar(CarDto car) -} ----- -==== - -The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingInheritanceStrategy()` configure when the method-level mapping configuration annotations are inherited from prototype methods in the interface to methods in the mapper: - -* `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 = ...)`. - -== Customizing mappings - -Sometimes it's needed to apply custom logic before or after certain mapping methods. MapStruct provides two ways for doing so: decorators which allow for a type-safe customization of specific mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a generic customization of mapping methods with given source or target types. - -[[customizing-mappers-using-decorators]] -=== Mapping customization with decorators - -In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can't be set by a generated method implementation. MapStruct supports this requirement using decorators. - -[TIP] -When working with the component model `cdi`, use https://docs.jboss.org/cdi/spec/1.0/html/decorators.html[CDI decorators] with MapStruct mappers instead of the `@DecoratedWith` annotation described here. - -To apply a decorator to a mapper class, specify it using the `@DecoratedWith` annotation. - -.Applying a decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -@DecoratedWith(PersonMapperDecorator.class) -public interface PersonMapper { - - PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); - - PersonDto personToPersonDto(Person person); - - AddressDto addressToAddressDto(Address address); -} ----- -==== - -The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class which allows to only implement those methods of the mapper interface which you want to customize. For all non-implemented methods, a simple delegation to the original mapper will be generated using the default generation routine. - -The `PersonMapperDecorator` shown below customizes the `personToPersonDto()`. It sets an additional attribute which is not present in the source type of the mapping. The `addressToAddressDto()` method is not customized. - -.Implementing a decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - private final PersonMapper delegate; - - public PersonMapperDecorator(PersonMapper delegate) { - this.delegate = delegate; - } - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setFullName( person.getFirstName() + " " + person.getLastName() ); - return dto; - } -} ----- -==== - -The example shows how you can optionally inject a delegate with the generated default implementation and use this delegate in your customized decorator methods. - -For a mapper with `componentModel = "default"`, define a constructor with a single parameter which accepts the type of the decorated mapper. - -When working with the component models `spring` or `jsr330`, this needs to be handled differently. - -[[decorators-with-spring]] -==== Decorators with the Spring component model - -When using `@DecoratedWith` on a mapper with component model `spring`, the generated implementation of the original mapper is annotated with the Spring annotation `@Qualifier("delegate")`. To autowire that bean in your decorator, add that qualifier annotation as well: - -.Spring-based decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - @Autowired - @Qualifier("delegate") - private PersonMapper delegate; - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setName( person.getFirstName() + " " + person.getLastName() ); - - return dto; - } - } ----- -==== - -The generated class that extends the decorator is annotated with Spring's `@Primary` annotation. To autowire the decorated mapper in the application, nothing special needs to be done: - -.Using a decorated mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Autowired -private PersonMapper personMapper; // injects the decorator, with the injected original mapper ----- -==== - -[[decorators-with-jsr-330]] -==== Decorators with the JSR 330 component model - -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 `@Named("fully-qualified-name-of-generated-implementation")` (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): - -.JSR 330 based decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - @Inject - @Named("org.examples.PersonMapperImpl_") - private PersonMapper delegate; - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setName( person.getFirstName() + " " + person.getLastName() ); - - return dto; - } -} ----- -==== - -Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for decorated mappers, the parameterless `@Named` annotation must be added to select the decorator to be injected: - -.Using a decorated mapper with JSR 330 -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Inject -@Named -private PersonMapper personMapper; // injects the decorator, with the injected original mapper ----- -==== - -[WARNING] -==== -`@DecoratedWith` in combination with component model `jsr330` is considered experimental as of the 1.0.0.CR2 release. The way the original mapper is referenced in the decorator or the way the decorated mapper is injected in the application code might still change. -==== - -[[customizing-mappings-with-before-and-after]] -=== Mapping customization with before-mapping and after-mapping methods - -Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can use *callback methods* that are invoked before the mapping starts or after the mapping finished. - -Callback methods can be implemented in the abstract mapper itself or in a type reference in `Mapper#uses`. - -.Mapper with @BeforeMapping and @AfterMapping hooks -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public abstract class VehicleMapper { - - @BeforeMapping - protected void flushEntity(AbstractVehicle vehicle) { - // I would call my entity manager's flush() method here to make sure my entity - // is populated with the right @Version before I let it map into the DTO - } - - @AfterMapping - protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) { - result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) ); - } - - public abstract CarDto toCarDto(Car car); -} - -// Generates something like this: -public class VehicleMapperImpl extends VehicleMapper { - - public CarDto toCarDto(Car car) { - flushEntity( car ); - - if ( car == null ) { - return null; - } - - CarDto carDto = new CarDto(); - // attributes mapping ... - - fillTank( car, carDto ); - - return carDto; - } -} ----- -==== - -If the `@BeforeMapping` / `@AfterMapping` method has parameters, the method invocation is only generated if all parameters can be *assigned* by the source or target parameters of the mapping method: - -* A parameter annotated with `@MappingTarget` is populated with the target instance of the mapping. -* A parameter annotated with `@TargetType` is populated with the target type of the mapping. -* Any other parameter is populated with a source parameter of the mapping, whereas each source parameter is used once at most. - -If the before/after-mapping method has a return type other than `void`, it will be checked to match the target type of the mapping methods. -Only the callback methods with a matching return type (or `void`) will be called in that mapping method. - -If a callback method returns a non-null value, this value will be returned from the mapping method. - -As with mapping methods, it is possible to specify type parameters for before/after-mapping methods. - -.Mapper with @AfterMapping hook that returns a non-null value -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public abstract class VehicleMapper { - - @PersistenceContext - private EntityManager entityManager; - - @AfterMapping - protected T attachEntity(@MappingTarget T entity) { - return entityManager.merge(entity); - } - - public abstract CarDto toCarDto(Car car); -} - -// Generates something like this: -public class VehicleMapperImpl extends VehicleMapper { - - public CarDto toCarDto(Car car) { - if ( car == null ) { - return null; - } - - CarDto carDto = new CarDto(); - // attributes mapping ... - - CarDto target = attachEntity( carDto ); - if ( target != null ) { - return target; - } - - return carDto; - } -} ----- -==== - -All before/after-mapping methods that *can* be applied to a mapping method *will* be used. <> can be used to further control which methods may be chosen and which not. For that, the qualifier annotation needs to be applied to the before/after-method and referenced in `BeanMapping#qualifiedBy` or `IterableMapping#qualifiedBy`. - -The order in which the selected methods are applied is roughly determined by their location of definition (although you should consider it a *code smell* if you need to rely on their order): - -* The order of methods within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation. -* Methods declared in one type are used after methods declared in their super-type. -* Methods implemented in the mapper itself are used before methods from types referenced in `Mapper#uses`. -* Types referenced in `Mapper#uses` are searched for before/after-mapping methods in the order specified in the annotation. - -[WARNING] -==== -`@BeforeMapping` and `@AfterMapping` are considered experimental as of the 1.0.0.CR1 release. Details in the selection of before/after mapping methods that are applicable for a mapping method or the order in which they are called might still be changed. -==== -[[using-spi]] -== Using the MapStruct SPI -=== Custom Accessor Naming Strategy - -MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provide 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. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class GolfPlayer { - - private double handicap; - private String name; - - public double handicap() { - return handicap; - } - - public GolfPlayer withHandicap(double handicap) { - this.handicap = handicap; - return this; - } - - public String name() { - return name; - } - - public GolfPlayer withName(String name) { - this.name = name; - return this; - } -} ----- -==== - -.Source object GolfPlayerDto with fluent API. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class GolfPlayerDto { - - private double handicap; - private String name; - - public double handicap() { - return handicap; - } - - public GolfPlayerDto withHandicap(double handicap) { - this.handicap = handicap; - return this; - } - - public String name() { - return name; - } - - public GolfPlayerDto withName(String name) { - this.name = name; - return this - } -} ----- -==== - -We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this: - -.Source object with fluent API. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface GolfPlayerMapper { - - GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class ); - - GolfPlayerDto toDto(GolfPlayer player); - - GolfPlayer toPlayer(GolfPlayerDto player); - -} ----- -==== - -This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`: - -.CustomAccessorNamingStrategy -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -/** - * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the - * form of {@code withProperty(value)}. - */ -public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy { - - @Override - public boolean isGetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID; - } - - @Override - public boolean isSetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - return methodName.startsWith( "with" ) && methodName.length() > 4; - } - - @Override - public String getPropertyName(ExecutableElement getterOrSetterMethod) { - String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); - } -} ----- -==== -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 seperate .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: There's the above example is present in our our examples repository (https://github.com/mapstruct/mapstruct-examples). +include::chapter-12-customizing-mapping.asciidoc[] +include::chapter-13-using-mapstruct-spi.asciidoc[] +include::chapter-14-third-party-api-integration.asciidoc[] \ No newline at end of file diff --git a/etc/ci-settings.xml b/etc/ci-settings.xml new file mode 100644 index 0000000000..e267e9cd4d --- /dev/null +++ b/etc/ci-settings.xml @@ -0,0 +1,11 @@ + + + + sonatype-nexus-snapshots + ${env.SONATYPE_USERNAME} + ${env.SONATYPE_PASSWORD} + + + diff --git a/etc/forbidden-apis.txt b/etc/forbidden-apis.txt new file mode 100644 index 0000000000..b0e7d62039 --- /dev/null +++ b/etc/forbidden-apis.txt @@ -0,0 +1,4 @@ + +@defaultMessage TypeMirror equals and hashCode are not stable. Types.isSameType(TypeMirror, TypeMirror) should be used +javax.lang.model.type.TypeMirror#hashCode() +javax.lang.model.type.TypeMirror#equals(java.lang.Object) diff --git a/etc/license.txt b/etc/license.txt index d0f5114fa6..66af6edf79 100644 --- a/etc/license.txt +++ b/etc/license.txt @@ -1,16 +1,3 @@ - Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - and/or other contributors as indicated by the @authors tag. See the - copyright.txt file in the distribution for a full listing of all - contributors. +Copyright MapStruct Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 diff --git a/etc/toolchains-cloudbees-jenkins.xml b/etc/toolchains-cloudbees-jenkins.xml deleted file mode 100644 index e6ce86e063..0000000000 --- a/etc/toolchains-cloudbees-jenkins.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - jdk - - 1.6 - oracle - jdk1.6 - - - /opt/jdk/jdk1.6.latest - - - - jdk - - 1.7 - oracle - jdk1.7 - - - /opt/jdk/jdk1.7.latest - - - - jdk - - 1.8 - oracle - jdk1.8 - - - /opt/jdk/jdk8.latest - - - - diff --git a/etc/toolchains-example.xml b/etc/toolchains-example.xml deleted file mode 100644 index 08cee8b241..0000000000 --- a/etc/toolchains-example.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - jdk - - 1.6.0_45 - oracle - jdk1.6 - - - C:\Program Files\Java\jdk1.6.0_45 - - - - jdk - - 1.7.0_51 - oracle - jdk1.7 - - - C:\Program Files\Java\jdk1.7.0_51 - - - - jdk - - 1.8.0_11 - oracle - jdk1.8 - - - C:\Program Files\Java\jdk1.8.0_11 - - - - jdk - - 1.9.0 - oracle - jdk1.9 - - - C:\Program Files\Java\jdk1.9.0 - - - diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 67fb9ba023..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -35,15 +22,24 @@ ${project.version} + true + + + + gradle + https://repo.gradle.org/artifactory/libs-releases/ + + true + + + false + + + - - junit - junit - test - org.assertj assertj-core @@ -54,6 +50,34 @@ maven-verifier test + + org.gradle + gradle-test-kit + 5.6.4 + test + + + org.gradle + gradle-tooling-api + 5.6.4 + test + + + commons-io + commons-io + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-engine + test + + @@ -71,21 +95,6 @@ \ - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - org.apache.maven.plugins maven-surefire-plugin @@ -98,19 +107,46 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + 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/CdiTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/CdiTest.java deleted file mode 100644 index 1ecda8a2d8..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/CdiTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "cdiTest", processorTypes = ProcessorType.ALL ) -public class CdiTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java new file mode 100644 index 0000000000..7d397c9499 --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -0,0 +1,71 @@ +/* + * 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 Andreas Gudian + */ +public final class FullFeatureCompilationExclusionCliEnhancer implements ProcessorTest.CommandLineEnhancer { + @Override + public Collection getAdditionalCommandLineArguments(ProcessorTest.ProcessorType processorType, + JRE currentJreVersion) { + List additionalExcludes = new ArrayList<>(); + + // 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()); + for ( int i = 0; i < additionalExcludes.size(); i++ ) { + result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) ); + } + + return result; + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java deleted file mode 100644 index 17b3f74011..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.tests.FullFeatureCompilationTest.CompilationExclusionCliEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.CommandLineEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * Integration test that compiles all test mappers in the processor-module, excluding all classes that contain one of - * the following in their path/file name: - *
    - *
  • {@code /erronerous/}
  • - *
  • {@code *Erroneous*}
  • - *
  • {@code *Test.java}
  • - *
  • {@code /testutil/}
  • - *
  • possibly more, depending on the processor type - see {@link CompilationExclusionCliEnhancer}
  • - *
- * - * @author Andreas Gudian - */ -@RunWith(ProcessorSuiteRunner.class) -@ProcessorSuite( - baseDir = "fullFeatureTest", - commandLineEnhancer = CompilationExclusionCliEnhancer.class, - processorTypes = { - ProcessorType.ORACLE_JAVA_6, - ProcessorType.ORACLE_JAVA_7, - ProcessorType.ORACLE_JAVA_8, - ProcessorType.ORACLE_JAVA_9, - ProcessorType.ECLIPSE_JDT_JAVA_6, - ProcessorType.ECLIPSE_JDT_JAVA_7, - ProcessorType.ECLIPSE_JDT_JAVA_8 -}) -public class FullFeatureCompilationTest { - /** - * Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers. - * - * @author Andreas Gudian - */ - public static final class CompilationExclusionCliEnhancer implements CommandLineEnhancer { - @Override - public Collection getAdditionalCommandLineArguments(ProcessorType processorType) { - List additionalExcludes = new ArrayList<>(); - - switch ( processorType ) { - case ORACLE_JAVA_6: - additionalExcludes.add( "org/mapstruct/ap/test/abstractclass/generics/*.java" ); - case ECLIPSE_JDT_JAVA_6: - case ORACLE_JAVA_7: - case ECLIPSE_JDT_JAVA_7: - additionalExcludes.add( "**/java8*/**/*.java" ); - break; - case ORACLE_JAVA_9: - // TODO find out why this fails: - additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); - 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; - } - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java new file mode 100644 index 0000000000..a12af7690f --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java @@ -0,0 +1,170 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +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 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.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) +class GradleIncrementalCompilationTest { + private static Path rootPath; + private static final String PROJECT_DIR = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; + private static final String COMPILE_TASK_NAME = "compileJava"; + + @TempDir + private File testBuildDir; + @TempDir + private File testProjectDir; + + private GradleRunner runner; + private File sourceDirectory; + private List compileArgs; // Gradle compile task arguments + + 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 { + String content = FileUtils.readFileToString( file, Charset.defaultCharset() ); + FileUtils.writeStringToFile( file, content.replace( target, replacement ), Charset.defaultCharset() ); + } + + private GradleRunner getRunner(String... additionalArguments) { + 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( ":" + COMPILE_TASK_NAME ).getOutcome() ); + } + + private void assertRecompiled(BuildResult result, int recompiledCount) { + assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE ); + assertThat( result.getOutput() ) + .contains( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ); + } + + private List buildCompileArgs() { + // Make Gradle use the temporary build folder by overriding the buildDir property + String buildDirPropertyArg = "-PbuildDir=" + testBuildDir.getAbsolutePath(); + + // Inject the path to the folder containing the mapstruct-processor JAR + String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString(); + return Arrays.asList( COMPILE_TASK_NAME, buildDirPropertyArg, jarDirectoryArg ); + } + + @BeforeAll + static void setupClass() { + rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath(); + } + + public void setup(String gradleVersion) throws IOException { + if ( !testBuildDir.exists() ) { + testBuildDir.mkdirs(); + } + + if ( !testProjectDir.exists() ) { + testProjectDir.mkdirs(); + } + // Copy test project files to the temp dir + Path gradleProjectPath = rootPath.resolve( PROJECT_DIR ); + FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir ); + compileArgs = buildCompileArgs(); + sourceDirectory = new File( testProjectDir, "src/main/java" ); + runner = GradleRunner.create().withGradleVersion( gradleVersion ).withProjectDir( testProjectDir ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + void testBuildSucceeds(String gradleVersion) throws IOException { + setup( gradleVersion ); + // Make sure the test build setup actually compiles + BuildResult buildResult = getRunner().build(); + assertCompileOutcome( buildResult, SUCCESS ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + void testUpToDate(String gradleVersion) throws IOException { + setup( gradleVersion ); + getRunner().build(); + BuildResult secondBuildResult = getRunner().build(); + assertCompileOutcome( secondBuildResult, UP_TO_DATE ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + void testChangeConstant(String gradleVersion) throws IOException { + setup( gradleVersion ); + getRunner().build(); + // Change return value in class Target + File targetFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/model/Target.java" ); + replaceInFile( targetFile, "original", "changed" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // 3 classes should be recompiled: Target -> TestMapper -> TestMapperImpl + assertRecompiled( secondBuildResult, 3 ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + void testChangeTargetField(String gradleVersion) throws IOException { + setup( gradleVersion ); + getRunner().build(); + // Change target field in mapper interface + File mapperFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/TestMapper.java" ); + replaceInFile( mapperFile, "field", "otherField" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // 2 classes should be recompiled: TestMapper -> TestMapperImpl + assertRecompiled( secondBuildResult, 2 ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + void testChangeUnrelatedFile(String gradleVersion) throws IOException { + setup( gradleVersion ); + getRunner().build(); + File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" ); + replaceInFile( unrelatedFile, "true", "false" ); + BuildResult secondBuildResult = getRunner( "--info" ).build(); + + // Only the UnrelatedComponent class should be recompiled + assertRecompiled( secondBuildResult, 1 ); + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/Java8Test.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/Java8Test.java deleted file mode 100644 index 45d5d480fe..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Java8Test.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "java8Test", processorTypes = ProcessorType.ALL_JAVA_8 ) -public class Java8Test { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/JaxbTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/JaxbTest.java deleted file mode 100644 index 912a3b7306..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/JaxbTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "jaxbTest", processorTypes = ProcessorType.ALL ) -public class JaxbTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/Jsr330Test.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/Jsr330Test.java deleted file mode 100644 index 726a715dfa..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Jsr330Test.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "jsr330Test", processorTypes = ProcessorType.ALL ) -public class Jsr330Test { -} 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 new file mode 100644 index 0000000000..042d6ec1da --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -0,0 +1,231 @@ +/* + * 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 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; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.mapstruct.itest.testutil.extension.ProcessorTest; + +/** + * @author Filip Hrisafov + */ +@Execution( ExecutionMode.CONCURRENT ) +public class MavenIntegrationTest { + + @ProcessorTest(baseDir = "autoValueBuilderTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC, + ProcessorTest.ProcessorType.ECLIPSE_JDT + }) + void autoValueBuilderTest() { + } + + @ProcessorTest(baseDir = "cdiTest") + void cdiTest() { + } + + /** + * See: https://github.com/mapstruct/mapstruct/issues/1121 + */ + @ProcessorTest(baseDir = "externalbeanjar", processorTypes = ProcessorTest.ProcessorType.JAVAC) + void externalBeanJarTest() { + } + + @ProcessorTest(baseDir = "freeBuilderBuilderTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + void freeBuilderBuilderTest() { + } + + /** + * Integration test that compiles all test mappers in the processor-module, excluding all classes that contain + * one of + * the following in their path/file name: + *

    + *
  • {@code /erronerous/}
  • + *
  • {@code *Erroneous*}
  • + *
  • {@code *Test.java}
  • + *
  • {@code /testutil/}
  • + *
  • possibly more, depending on the processor type - see {@link FullFeatureCompilationExclusionCliEnhancer}
  • + *
+ */ + @ProcessorTest(baseDir = "fullFeatureTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC, + ProcessorTest.ProcessorType.ECLIPSE_JDT + }, commandLineEnhancer = FullFeatureCompilationExclusionCliEnhancer.class) + void fullFeatureTest() { + } + + @ProcessorTest(baseDir = "immutablesBuilderTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC, + ProcessorTest.ProcessorType.ECLIPSE_JDT + }) + void immutablesBuilderTest() { + } + + @ProcessorTest(baseDir = "java8Test") + void java8Test() { + } + + @ProcessorTest(baseDir = "jaxbTest") + 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 + }) + void namingStrategyTest() { + } + + /** + * ECLIPSE_JDT is not working with Protobuf. Use all other available processor types. + */ + @ProcessorTest(baseDir = "protobufBuilderTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + void protobufBuilderTest() { + } + + @ProcessorTest(baseDir = "sealedSubclassTest") + @EnabledForJreRange(min = JRE.JAVA_17) + void sealedSubclassTest() { + } + + @ProcessorTest(baseDir = "recordsTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + @EnabledForJreRange(min = JRE.JAVA_14) + void recordsTest() { + } + + @ProcessorTest(baseDir = "recordsCrossModuleTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + @EnabledForJreRange(min = JRE.JAVA_17) + 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() { + } + + /** + * Tests usage of MapStruct with another processor that generates supertypes of mapping source/target types. + */ + @ProcessorTest(baseDir = "superTypeGenerationTest", processorTypes = ProcessorTest.ProcessorType.JAVAC) + void superTypeGenerationTest() { + } + + /** + * Tests usage of MapStruct with another processor that generates the target type of a mapping method. + */ + @ProcessorTest(baseDir = "targetTypeGenerationTest", processorTypes = ProcessorTest.ProcessorType.JAVAC) + void targetTypeGenerationTest() { + } + + @ProcessorTest(baseDir = "moduleInfoTest") + @EnabledForJreRange(min = JRE.JAVA_11) + void moduleInfoTest() { + + } + + /** + * Tests usage of MapStruct with another processor that generates the uses type of a mapper. + */ + @ProcessorTest(baseDir = "usesTypeGenerationTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + void usesTypeGenerationTest() { + } + + /** + * Tests usage of MapStruct with another processor that generates the uses type of a mapper. + */ + @ProcessorTest(baseDir = "usesTypeGenerationTest", processorTypes = { + ProcessorTest.ProcessorType.ECLIPSE_JDT + }) + @EnabledForJreRange(min = JRE.JAVA_11) + // For some reason the second run with eclipse does not load the ModelElementProcessor(s) on java 8, + // therefore we run this only on Java 11 + void usesTypeGenerationTestEclipse() { + } + + /** + * Tests usage of MapStruct with faulty provider of AstModifyingAnnotationProcessor. + */ + @ProcessorTest(baseDir = "faultyAstModifyingAnnotationProcessorTest") + void faultyAstModifyingProcessor() { + } + +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/NamingStrategyTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/NamingStrategyTest.java deleted file mode 100644 index 0b61054f74..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/NamingStrategyTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "namingStrategyTest", processorTypes = ProcessorType.ORACLE_JAVA_8) -public class NamingStrategyTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/SimpleTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/SimpleTest.java deleted file mode 100644 index e93af23a94..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SimpleTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "simpleTest", processorTypes = ProcessorType.ALL ) -public class SimpleTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/SpringTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/SpringTest.java deleted file mode 100644 index 1b76ca6e23..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SpringTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "springTest", processorTypes = ProcessorType.ALL ) -public class SpringTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java deleted file mode 100644 index 632a8b78b7..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.tests; - -import org.junit.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * Tests usage of MapStruct with another processor that generates supertypes of mapping source/target types. - * - * @author Gunnar Morling - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "superTypeGenerationTest", processorTypes = ProcessorType.ORACLE_JAVA_8) -public class SuperTypeGenerationTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorEnabledOnJreCondition.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorEnabledOnJreCondition.java new file mode 100644 index 0000000000..a651e55f93 --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorEnabledOnJreCondition.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.testutil.extension; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import static org.mapstruct.itest.testutil.extension.ProcessorTestTemplateInvocationContext.CURRENT_VERSION; + +/** + * @author Filip Hrisafov + */ +public class ProcessorEnabledOnJreCondition implements ExecutionCondition { + + static final ConditionEvaluationResult ENABLED_ON_CURRENT_JRE = + ConditionEvaluationResult.enabled( "Enabled on JRE version: " + System.getProperty( "java.version" ) ); + + static final ConditionEvaluationResult DISABLED_ON_CURRENT_JRE = + ConditionEvaluationResult.disabled( "Disabled on JRE version: " + System.getProperty( "java.version" ) ); + + public ProcessorEnabledOnJreCondition(ProcessorTest.ProcessorType processorType) { + this.processorType = processorType; + } + + protected final ProcessorTest.ProcessorType processorType; + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + // If the max JRE is greater or equal to the current version the test is enabled + return processorType.maxJre().compareTo( CURRENT_VERSION ) >= 0 ? ENABLED_ON_CURRENT_JRE : + DISABLED_ON_CURRENT_JRE; + } +} 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 new file mode 100644 index 0000000000..39cd5fdae6 --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -0,0 +1,218 @@ +/* + * 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.testutil.extension; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.maven.it.Verifier; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.platform.commons.util.ReflectionUtils; + +import static org.apache.maven.it.util.ResourceExtractor.extractResourceToDestination; +import static org.apache.maven.shared.utils.io.FileUtils.copyURLToFile; +import static org.apache.maven.shared.utils.io.FileUtils.deleteDirectory; +import static org.mapstruct.itest.testutil.extension.ProcessorTestTemplateInvocationContext.CURRENT_VERSION; + +/** + * @author Filip Hrisafov + * @author Andreas Gudian + */ +public class ProcessorInvocationInterceptor implements InvocationInterceptor { + + /** + * System property to enable remote debugging of the processor execution in the integration test + */ + public static final String SYS_PROP_DEBUG = "processorIntegrationTest.debug"; + + private final ProcessorTestContext processorTestContext; + + public ProcessorInvocationInterceptor(ProcessorTestContext processorTestContext) { + this.processorTestContext = processorTestContext; + } + + @Override + public void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + try { + doExecute( extensionContext ); + invocation.proceed(); + } + catch ( Exception e ) { + invocation.skip(); + throw e; + } + } + + private void doExecute(ExtensionContext extensionContext) throws Exception { + File destination = extractTest( extensionContext ); + PrintStream originalOut = System.out; + + final Verifier verifier; + if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { + // the compiler is executed within the Maven JVM. So make + // sure we fork a new JVM for that, and let that new JVM use the command 'mvnDebug' instead of 'mvn' + verifier = new Verifier( destination.getCanonicalPath(), null, true, true ); + verifier.setDebugJvm( true ); + } + else { + verifier = new Verifier( destination.getCanonicalPath() ); + if ( processorTestContext.isForkJvm() ) { + verifier.setForkJvm( true ); + } + } + + List goals = new ArrayList<>( 3 ); + + goals.add( "clean" ); + + try { + configureProcessor( verifier ); + + verifier.addCliOption( "-Dcompiler-source-target-version=" + sourceTargetVersion() ); + + if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { + originalOut.print( "Processor Integration Test: " ); + originalOut.println( "Listening for transport dt_socket at address: 8000 (in some seconds)" ); + } + + goals.add( "test" ); + + addAdditionalCliArguments( verifier ); + + originalOut.println( extensionContext.getRequiredTestClass().getSimpleName() + "." + + extensionContext.getRequiredTestMethod().getName() + " executing " + + processorTestContext.getProcessor().name().toLowerCase() ); + + verifier.executeGoals( goals ); + verifier.verifyErrorFreeLog(); + } + finally { + verifier.resetStreams(); + } + } + + private void addAdditionalCliArguments(Verifier verifier) + throws Exception { + Class cliEnhancerClass = + processorTestContext.getCliEnhancerClass(); + + Constructor cliEnhancerConstructor = null; + if ( cliEnhancerClass != ProcessorTest.CommandLineEnhancer.class ) { + try { + cliEnhancerConstructor = cliEnhancerClass.getConstructor(); + ProcessorTest.CommandLineEnhancer enhancer = cliEnhancerConstructor.newInstance(); + Collection additionalArgs = enhancer.getAdditionalCommandLineArguments( + processorTestContext.getProcessor(), CURRENT_VERSION ); + + for ( String arg : additionalArgs ) { + verifier.addCliOption( arg ); + } + + } + catch ( NoSuchMethodException e ) { + throw new RuntimeException( cliEnhancerClass + " does not have a default constructor." ); + } + catch ( SecurityException e ) { + throw new RuntimeException( e ); + } + } + } + + private void configureProcessor(Verifier verifier) { + 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" ); + } + } + } + + private File extractTest(ExtensionContext extensionContext) throws IOException { + String tmpDir = getTmpDir(); + + String tempDirName = extensionContext.getRequiredTestClass().getPackage().getName() + "." + + extensionContext.getRequiredTestMethod().getName(); + File tempDirBase = new File( tmpDir, tempDirName ).getCanonicalFile(); + + if ( !tempDirBase.exists() ) { + tempDirBase.mkdirs(); + } + + File parentPom = new File( tempDirBase, "pom.xml" ); + copyURLToFile( getClass().getResource( "/pom.xml" ), parentPom ); + + ProcessorTest.ProcessorType processorType = processorTestContext.getProcessor(); + File tempDir = new File( tempDirBase, processorType.name().toLowerCase() ); + deleteDirectory( tempDir ); + + return extractResourceToDestination( getClass(), "/" + processorTestContext.getBaseDir(), tempDir, true ); + } + + private String getTmpDir() { + if ( CURRENT_VERSION == JRE.JAVA_8 ) { + // On Java 8 the tmp dir is always + // no matter we run from the aggregator or not + return "target/tmp"; + } + + // On Java 11+ we need to do it base on the location relative + String tmpDir; + if ( Files.exists( Paths.get( "integrationtest" ) ) ) { + // If it exists then we are running from the main aggregator + tmpDir = "integrationtest/target/tmp"; + } + else { + tmpDir = "target/tmp"; + } + return tmpDir; + } + + private String sourceTargetVersion() { + if ( CURRENT_VERSION == JRE.JAVA_8 ) { + return "1.8"; + } + else if ( CURRENT_VERSION == JRE.OTHER ) { + try { + // Extracting the major version is done with code from + // org.junit.jupiter.api.condition.JRE when determining the current version + + // java.lang.Runtime.version() is a static method available on Java 9+ + // that returns an instance of java.lang.Runtime.Version which has the + // following method: public int major() + Method versionMethod = null; + versionMethod = Runtime.class.getMethod( "version" ); + Object version = ReflectionUtils.invokeMethod( versionMethod, null ); + Method majorMethod = version.getClass().getMethod( "major" ); + return String.valueOf( (int) ReflectionUtils.invokeMethod( majorMethod, version ) ); + } + catch ( NoSuchMethodException e ) { + throw new RuntimeException( "Failed to get Java Version" ); + } + } + else { + return CURRENT_VERSION.name().substring( 5 ); + } + } +} 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 new file mode 100644 index 0000000000..d5b4860d5b --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java @@ -0,0 +1,121 @@ +/* + * 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.testutil.extension; + +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; +import java.util.Collection; + +import org.apache.maven.it.Verifier; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Declares the content of the integration test. + *

+ * {@link #baseDir()} must be a path in the classpath that contains the maven module to run as integration test. The + * integration test module should contain at least one test class. The integration test passes, if + * {@code mvn clean test} finishes successfully. + *

+ * {@link #processorTypes()} configures the variants to execute the integration tests with. See + * {@link ProcessorType}. + * + * @author Filip Hrisafov + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@TestTemplate +@ExtendWith(ProcessorTestTemplateInvocationContextProvider.class) +public @interface ProcessorTest { + + /** + * Describes the type of the processing variant(s) to use when executing the integration test. + * + * @author Filip Hrisafov + */ + enum ProcessorType { + + JAVAC( "javac" ), + JAVAC_WITH_PATHS( "javac", JRE.OTHER, "generate-via-compiler-plugin-with-annotation-processor-paths" ), + + ECLIPSE_JDT( "jdt", JRE.JAVA_8 ); + + private final String compilerId; + private final JRE max; + private final String profile; + + ProcessorType(String compilerId) { + this( compilerId, JRE.OTHER ); + } + + ProcessorType(String compilerId, JRE max) { + this( compilerId, max, null ); + } + + ProcessorType(String compilerId, JRE max, String profile) { + this.compilerId = compilerId; + this.max = max; + this.profile = profile; + } + + public String getCompilerId() { + return compilerId; + } + + public JRE maxJre() { + return max; + } + + public String getProfile() { + return profile; + } + } + + /** + * Can be configured to provide additional command line arguments for the invoked Maven process, depending on the + * {@link ProcessorType} the test is executed for. + * + * @author Andreas Gudian + */ + interface CommandLineEnhancer { + /** + * @param processorType the processor type for which the test is executed. + * @param currentJreVersion the current JRE version + * + * @return additional command line arguments to be passed to the Maven {@link Verifier}. + */ + Collection getAdditionalCommandLineArguments(ProcessorType processorType, + JRE currentJreVersion); + } + + + /** + * @return a path in the classpath that contains the maven module to run as integration test: {@code mvn clean test} + */ + String baseDir(); + + /** + * @return the variants to execute the integration tests with. See {@link ProcessorType}. + */ + ProcessorType[] processorTypes() default { + ProcessorType.JAVAC, + ProcessorType.JAVAC_WITH_PATHS, + ProcessorType.ECLIPSE_JDT, + }; + + /** + * @return the {@link CommandLineEnhancer} implementation. Must have a default constructor. + */ + Class commandLineEnhancer() default CommandLineEnhancer.class; + + boolean forkJvm() default false; + +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java new file mode 100644 index 0000000000..feb5c7dc02 --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestContext.java @@ -0,0 +1,43 @@ +/* + * 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.testutil.extension; + +/** + * @author Filip Hrisafov + */ +public class ProcessorTestContext { + + private final String baseDir; + private final ProcessorTest.ProcessorType processor; + private final Class cliEnhancerClass; + private final boolean forkJvm; + + public ProcessorTestContext(String baseDir, + ProcessorTest.ProcessorType processor, + Class cliEnhancerClass, + boolean forkJvm) { + this.baseDir = baseDir; + this.processor = processor; + this.cliEnhancerClass = cliEnhancerClass; + this.forkJvm = forkJvm; + } + + public String getBaseDir() { + return baseDir; + } + + public ProcessorTest.ProcessorType getProcessor() { + return processor; + } + + public Class getCliEnhancerClass() { + return cliEnhancerClass; + } + + public boolean isForkJvm() { + return forkJvm; + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContext.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContext.java new file mode 100644 index 0000000000..47e692988d --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContext.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.testutil.extension; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; + +/** + * @author Filip Hrisafov + */ +public class ProcessorTestTemplateInvocationContext implements TestTemplateInvocationContext { + + static final JRE CURRENT_VERSION; + + static { + JRE currentVersion = JRE.OTHER; + for ( JRE jre : JRE.values() ) { + if ( jre.isCurrentVersion() ) { + currentVersion = jre; + break; + } + } + + CURRENT_VERSION = currentVersion; + } + + private final ProcessorTestContext processorTestContext; + + public ProcessorTestTemplateInvocationContext(ProcessorTestContext processorTestContext) { + this.processorTestContext = processorTestContext; + } + + @Override + public String getDisplayName(int invocationIndex) { + return processorTestContext.getProcessor().name().toLowerCase(); + } + + @Override + public List getAdditionalExtensions() { + List extensions = new ArrayList<>(); + extensions.add( new ProcessorEnabledOnJreCondition( processorTestContext.getProcessor() ) ); + extensions.add( new ProcessorInvocationInterceptor( processorTestContext ) ); + return extensions; + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.java new file mode 100644 index 0000000000..5ceb44804d --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTestTemplateInvocationContextProvider.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.testutil.extension; + +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * @author Filip Hrisafov + */ +public class ProcessorTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider { + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return AnnotationSupport.isAnnotated( context.getTestMethod(), ProcessorTest.class ); + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + + Method testMethod = context.getRequiredTestMethod(); + ProcessorTest processorTest = AnnotationSupport.findAnnotation( testMethod, ProcessorTest.class ) + .orElseThrow( () -> new RuntimeException( "Failed to get ProcessorTest on " + testMethod ) ); + + + return Stream.of( processorTest.processorTypes() ) + .map( processorType -> new ProcessorTestTemplateInvocationContext( new ProcessorTestContext( + processorTest.baseDir(), + processorType, + processorTest.commandLineEnhancer(), + processorTest.forkJvm() + ) ) ); + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuite.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuite.java deleted file mode 100644 index 0726b396e9..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuite.java +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.testutil.runner; - -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; -import java.util.Collection; - -import org.apache.maven.it.Verifier; - -/** - * Declares the content of the integration test. - *

- * {@link #baseDir()} must be a path in the classpath that contains the maven module to run as integration test. The - * integration test module should contain at least one test class. The integration test passes, if - * {@code mvn clean test} finishes successfully. - *

- * {@link #processorTypes()} configures the variants to execute the integration tests with. See {@link ProcessorType}. - * - * @author Andreas Gudian - */ -@Retention( RetentionPolicy.RUNTIME ) -@Documented -@Target( ElementType.TYPE ) -public @interface ProcessorSuite { - /** - * Describes the type of the processing variant(s) to use when executing the integration test. - *

- * Types that require toolchains, will - * need the toolchains.xml file to be either installed in ~/m2, or alternatively passed to the mvn process using - * {@code mvn -DprocessorIntegrationTest.toolchainsFile=/path/to/toolchains.xml ...} - * - * @author Andreas Gudian - */ - enum ProcessorType { - /** - * Use an Oracle JDK 1.6 (or 1.6.x) via toolchain support to perform the processing - */ - ORACLE_JAVA_6( new Toolchain( "oracle", "1.6", "1.7" ), "javac", "1.6" ), - - /** - * Use an Oracle JDK 1.7 (or 1.7.x) via toolchain support to perform the processing - */ - ORACLE_JAVA_7( new Toolchain( "oracle", "1.7", "1.8" ), "javac", "1.7" ), - - /** - * Use the same JDK that runs the mvn build to perform the processing - */ - ORACLE_JAVA_8( null, "javac", "1.8" ), - - /** - * Use an Oracle JDK 1.9 (or 1.9.x) via toolchain support to perform the processing - */ - ORACLE_JAVA_9( new Toolchain( "oracle", "9", "10" ), "javac", "1.9" ), - - /** - * Use the eclipse compiler with 1.6 source/target level from tycho-compiler-jdt to perform the build and - * processing - */ - ECLIPSE_JDT_JAVA_6( null, "jdt", "1.6" ), - - /** - * Use the eclipse compiler with 1.7 source/target level from tycho-compiler-jdt to perform the build and - * processing - */ - ECLIPSE_JDT_JAVA_7( null, "jdt", "1.7" ), - - /** - * Use the eclipse compiler with 1.8 source/target level from tycho-compiler-jdt to perform the build and - * processing - */ - ECLIPSE_JDT_JAVA_8( null, "jdt", "1.8" ), - - /** - * Use the maven-processor-plugin with 1.8 source/target level with the same JDK that runs the mvn build to - * perform the processing - */ - PROCESSOR_PLUGIN_JAVA_8( null, null, "1.8" ), - - /** - * Use all available processing variants - */ - ALL( ORACLE_JAVA_6, ORACLE_JAVA_7, ORACLE_JAVA_8, ORACLE_JAVA_9, ECLIPSE_JDT_JAVA_6, ECLIPSE_JDT_JAVA_7, - ECLIPSE_JDT_JAVA_8, PROCESSOR_PLUGIN_JAVA_8 ), - - /** - * Use all JDK8 compatible processing variants - */ - ALL_JAVA_8( ORACLE_JAVA_8, ECLIPSE_JDT_JAVA_8, PROCESSOR_PLUGIN_JAVA_8 ); - - private ProcessorType[] included = { }; - - private Toolchain toolchain; - private String compilerId; - private String sourceTargetVersion; - - ProcessorType(Toolchain toolchain, String compilerId, String sourceTargetVersion) { - this.toolchain = toolchain; - this.compilerId = compilerId; - this.sourceTargetVersion = sourceTargetVersion; - } - - ProcessorType(ProcessorType... included) { - this.included = included; - } - - /** - * @return the processor types that are grouped by this type - */ - public ProcessorType[] getIncluded() { - return included; - } - - /** - * @return the toolchain - */ - public Toolchain getToolchain() { - return toolchain; - } - - /** - * @return the compilerId - */ - public String getCompilerId() { - return compilerId; - } - - /** - * @return the sourceTargetVersion - */ - public String getSourceTargetVersion() { - return sourceTargetVersion; - } - } - - /** - * Can be configured to provide additional command line arguments for the invoked Maven process, depending on the - * {@link ProcessorType} the test is executed for. - * - * @author Andreas Gudian - */ - interface CommandLineEnhancer { - /** - * @param processorType the processor type for which the test is executed. - * @return additional command line arguments to be passed to the Maven {@link Verifier}. - */ - Collection getAdditionalCommandLineArguments(ProcessorType processorType); - } - - /** - * @return a path in the classpath that contains the maven module to run as integration test: {@code mvn clean test} - */ - String baseDir(); - - /** - * @return the variants to execute the integration tests with. See {@link ProcessorType}. - */ - ProcessorType[] processorTypes() default { ProcessorType.ALL }; - - /** - * @return the {@link CommandLineEnhancer} implementation. Must have a default constructor. - */ - Class commandLineEnhancer() default CommandLineEnhancer.class; -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java deleted file mode 100644 index 05203d120b..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.testutil.runner; - -import static org.apache.maven.it.util.ResourceExtractor.extractResourceToDestination; -import static org.apache.maven.shared.utils.io.FileUtils.copyURLToFile; -import static org.apache.maven.shared.utils.io.FileUtils.deleteDirectory; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; - -import org.apache.maven.it.Verifier; -import org.junit.internal.AssumptionViolatedException; -import org.junit.internal.runners.model.EachTestNotifier; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; -import org.junit.runner.notification.StoppedByUserException; -import org.junit.runners.ParentRunner; -import org.junit.runners.model.InitializationError; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.CommandLineEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner.ProcessorTestCase; -import org.mapstruct.itest.testutil.runner.xml.Toolchains; -import org.mapstruct.itest.testutil.runner.xml.Toolchains.ProviderDescription; - -/** - * Runner for processor integration tests. Requires the annotation {@link ProcessorSuite} on the test class. - * - * @author Andreas Gudian - */ -public class ProcessorSuiteRunner extends ParentRunner { - - /** - * System property for specifying the location of the toolchains.xml file - */ - public static final String SYS_PROP_TOOLCHAINS_FILE = "processorIntegrationTest.toolchainsFile"; - - /** - * System property to enable remote debugging of the processor execution in the integration test - */ - public static final String SYS_PROP_DEBUG = "processorIntegrationTest.debug"; - - private static final File TOOLCHAINS_FILE = getToolchainsFile(); - - private static final Collection ENABLED_PROCESSOR_TYPES = detectSupportedTypes( TOOLCHAINS_FILE ); - - public static final class ProcessorTestCase { - private final String baseDir; - private final ProcessorType processor; - private final boolean ignored; - private final Constructor cliEnhancerConstructor; - - public ProcessorTestCase(String baseDir, ProcessorType processor, - Constructor cliEnhancerConstructor) { - this.baseDir = baseDir; - this.processor = processor; - this.cliEnhancerConstructor = cliEnhancerConstructor; - this.ignored = !ENABLED_PROCESSOR_TYPES.contains( processor ); - } - } - - private final List methods; - - /** - * @param clazz the test class - * @throws InitializationError in case the initialization fails - */ - public ProcessorSuiteRunner(Class clazz) throws InitializationError { - super( clazz ); - - ProcessorSuite suite = clazz.getAnnotation( ProcessorSuite.class ); - - if ( null == suite ) { - throw new InitializationError( "The test class must be annotated with " + ProcessorSuite.class.getName() ); - } - - if ( suite.processorTypes().length == 0 ) { - throw new InitializationError( "ProcessorSuite#processorTypes must not be empty" ); - } - - Constructor cliEnhancerConstructor = null; - if ( suite.commandLineEnhancer() != CommandLineEnhancer.class ) { - try { - cliEnhancerConstructor = suite.commandLineEnhancer().getConstructor(); - } - catch ( NoSuchMethodException e ) { - throw new InitializationError( - suite.commandLineEnhancer().getName() + " does not have a default constructor." ); - } - catch ( SecurityException e ) { - throw new InitializationError( e ); - } - } - - methods = initializeTestCases( suite, cliEnhancerConstructor ); - } - - private List initializeTestCases(ProcessorSuite suite, - Constructor cliEnhancerConstructor) { - List types = new ArrayList(); - - for ( ProcessorType compiler : suite.processorTypes() ) { - if ( compiler.getIncluded().length > 0 ) { - types.addAll( Arrays.asList( compiler.getIncluded() ) ); - } - else { - types.add( compiler ); - } - } - - List result = new ArrayList( types.size() ); - - for ( ProcessorType type : types ) { - result.add( new ProcessorTestCase( suite.baseDir(), type, cliEnhancerConstructor ) ); - } - - return result; - } - - @Override - protected List getChildren() { - return methods; - } - - @Override - protected Description describeChild(ProcessorTestCase child) { - return Description.createTestDescription( getTestClass().getJavaClass(), child.processor.name().toLowerCase() ); - } - - @Override - protected void runChild(ProcessorTestCase child, RunNotifier notifier) { - Description description = describeChild( child ); - EachTestNotifier testNotifier = new EachTestNotifier( notifier, description ); - - if ( child.ignored ) { - testNotifier.fireTestIgnored(); - } - else { - try { - testNotifier.fireTestStarted(); - doExecute( child, description ); - } - catch ( AssumptionViolatedException e ) { - testNotifier.fireTestIgnored(); - } - catch ( StoppedByUserException e ) { - throw e; - } - catch ( Throwable e ) { - testNotifier.addFailure( e ); - } - finally { - testNotifier.fireTestFinished(); - } - } - } - - private void doExecute(ProcessorTestCase child, Description description) throws Exception { - File destination = extractTest( child, description ); - PrintStream originalOut = System.out; - - final Verifier verifier; - if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { - if ( child.processor.getToolchain() == null ) { - // when not using toolchains for a test, then the compiler is executed within the Maven JVM. So make - // sure we fork a new JVM for that, and let that new JVM use the command 'mvnDebug' instead of 'mvn' - verifier = new Verifier( destination.getCanonicalPath(), null, true, true ); - verifier.setDebugJvm( true ); - } - else { - verifier = new Verifier( destination.getCanonicalPath() ); - verifier.addCliOption( "-Pdebug-forked-javac" ); - } - } - else { - verifier = new Verifier( destination.getCanonicalPath() ); - } - - List goals = new ArrayList( 3 ); - - goals.add( "clean" ); - - try { - configureToolchains( child, verifier, goals, originalOut ); - configureProcessor( child, verifier ); - - verifier.addCliOption( "-Dcompiler-source-target-version=" + child.processor.getSourceTargetVersion() ); - - if ( "1.8".equals( child.processor.getSourceTargetVersion() ) - || "1.9".equals( child.processor.getSourceTargetVersion() ) ) { - verifier.addCliOption( "-Dmapstruct-artifact-id=mapstruct-jdk8" ); - } - else { - verifier.addCliOption( "-Dmapstruct-artifact-id=mapstruct" ); - } - - if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { - originalOut.print( "Processor Integration Test: " ); - originalOut.println( "Listening for transport dt_socket at address: 8000 (in some seconds)" ); - } - - goals.add( "test" ); - - addAdditionalCliArguments( child, verifier ); - - originalOut.println( "executing " + child.processor.name().toLowerCase() ); - - verifier.executeGoals( goals ); - verifier.verifyErrorFreeLog(); - } - finally { - verifier.resetStreams(); - } - } - - private void addAdditionalCliArguments(ProcessorTestCase child, final Verifier verifier) throws Exception { - if ( child.cliEnhancerConstructor != null ) { - CommandLineEnhancer enhancer = child.cliEnhancerConstructor.newInstance(); - Collection additionalArgs = enhancer.getAdditionalCommandLineArguments( child.processor ); - - if ( additionalArgs != null ) { - for ( String arg : additionalArgs ) { - verifier.addCliOption( arg ); - } - } - } - } - - private void configureProcessor(ProcessorTestCase child, Verifier verifier) { - if ( child.processor.getCompilerId() != null ) { - verifier.addCliOption( "-Pgenerate-via-compiler-plugin" ); - verifier.addCliOption( "-Dcompiler-id=" + child.processor.getCompilerId() ); - } - else { - verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); - } - } - - private void configureToolchains(ProcessorTestCase child, Verifier verifier, List goals, - PrintStream originalOut) { - if ( child.processor.getToolchain() != null ) { - verifier.addCliOption( "--toolchains" ); - verifier.addCliOption( TOOLCHAINS_FILE.getPath().replace( '\\', '/' ) ); - - Toolchain toolchain = child.processor.getToolchain(); - - verifier.addCliOption( "-Dtoolchain-jdk-vendor=" + toolchain.getVendor() ); - verifier.addCliOption( "-Dtoolchain-jdk-version=" + toolchain.getVersionRangeString() ); - - goals.add( "toolchains:toolchain" ); - } - } - - private File extractTest(ProcessorTestCase child, Description description) throws IOException { - File tempDirBase = new File( "target/tmp", description.getClassName() ).getCanonicalFile(); - - if ( !tempDirBase.exists() ) { - tempDirBase.mkdirs(); - } - - File parentPom = new File( tempDirBase, "pom.xml" ); - copyURLToFile( getClass().getResource( "/pom.xml" ), parentPom ); - - File tempDir = new File( tempDirBase, description.getMethodName() ); - deleteDirectory( tempDir ); - - return extractResourceToDestination( getClass(), "/" + child.baseDir, tempDir, true ); - } - - private static File getToolchainsFile() { - String specifiedToolchainsFile = System.getProperty( SYS_PROP_TOOLCHAINS_FILE ); - if ( null != specifiedToolchainsFile ) { - try { - File canonical = new File( specifiedToolchainsFile ).getCanonicalFile(); - if ( canonical.exists() ) { - return canonical; - } - - // check the path relative to the parent directory (allows specifying a path relative to the top-level - // aggregator module) - canonical = new File( "..", specifiedToolchainsFile ).getCanonicalFile(); - if ( canonical.exists() ) { - return canonical; - } - } - catch ( IOException e ) { - return null; - } - } - - // use default location - String defaultPath = System.getProperty( "user.home" ) + System.getProperty( "file.separator" ) + ".m2"; - return new File( defaultPath, "toolchains.xml" ); - } - - private static Collection detectSupportedTypes(File toolchainsFile) { - Toolchains toolchains; - try { - if ( toolchainsFile.exists() ) { - Unmarshaller unmarshaller = JAXBContext.newInstance( Toolchains.class ).createUnmarshaller(); - toolchains = (Toolchains) unmarshaller.unmarshal( toolchainsFile ); - } - else { - toolchains = null; - } - } - catch ( JAXBException e ) { - toolchains = null; - } - - Collection supported = new HashSet<>(); - for ( ProcessorType type : ProcessorType.values() ) { - if ( isSupported( type, toolchains ) ) { - supported.add( type ); - } - } - - return supported; - } - - private static boolean isSupported(ProcessorType type, Toolchains toolchains) { - if ( type.getToolchain() == null ) { - return true; - } - - if ( toolchains == null ) { - return false; - } - - Toolchain required = type.getToolchain(); - - for ( Toolchains.Toolchain tc : toolchains.getToolchains() ) { - if ( "jdk".equals( tc.getType() ) ) { - ProviderDescription desc = tc.getProviderDescription(); - if ( desc != null - && required.getVendor().equals( desc.getVendor() ) - && desc.getVersion() != null - && desc.getVersion().startsWith( required.getVersionMinInclusive() ) ) { - return true; - } - } - } - - return false; - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java deleted file mode 100644 index 9e779d82d0..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.testutil.runner; - -/** - * Describes a required entry in the Maven toolchains.xml file. - * - * @author Andreas Gudian - */ -class Toolchain { - private final String vendor; - private final String versionMinInclusive; - private final String versionMaxExclusive; - - Toolchain(String vendor, String versionMinInclusive, String versionMaxExclusive) { - this.vendor = vendor; - this.versionMinInclusive = versionMinInclusive; - this.versionMaxExclusive = versionMaxExclusive; - } - - String getVendor() { - return vendor; - } - - String getVersionMinInclusive() { - return versionMinInclusive; - } - - /** - * @return the version range string to be used in the Maven execution to select the toolchain - */ - String getVersionRangeString() { - return "[" + versionMinInclusive + "," + versionMaxExclusive + ")"; - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java deleted file mode 100644 index 352fed6dd6..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.testutil.runner.xml; - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * JAXB representation of some of the parts in the Maven toolchains.xml file. - * - * @author Andreas Gudian - */ -@XmlRootElement -public class Toolchains { - @XmlElement(name = "toolchain") - private List toolchains = new ArrayList<>(); - - public List getToolchains() { - return toolchains; - } - - @Override - public String toString() { - return "Toolchains [toolchains=" + toolchains + "]"; - } - - public static class Toolchain { - @XmlElement - private String type; - - @XmlElement(name = "provides") - private ProviderDescription providerDescription; - - public String getType() { - return type; - } - - public ProviderDescription getProviderDescription() { - return providerDescription; - } - - @Override - public String toString() { - return "Toolchain [type=" + type + ", providerDescription=" + providerDescription + "]"; - } - } - - public static class ProviderDescription { - @XmlElement - private String version; - - @XmlElement - private String vendor; - - public String getVersion() { - return version; - } - - public String getVendor() { - return vendor; - } - - @Override - public String toString() { - return "ProviderDescription [version=" + version + ", vendor=" + vendor + "]"; - } - } -} diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/pom.xml b/integrationtest/src/test/resources/autoValueBuilderTest/pom.xml new file mode 100644 index 0000000000..383ab4efbe --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + autoValueIntegrationTest + jar + + + + com.google.auto.value + auto-value + provided + + + diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Address.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Address.java new file mode 100644 index 0000000000..97eb723193 --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Address.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.auto.value; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class Address { + public abstract String getAddressLine(); + + public static Builder builder() { + return new AutoValue_Address.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder addressLine(String addressLine); + + public abstract Address build(); + } +} diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/AddressDto.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/AddressDto.java new file mode 100644 index 0000000000..35eed1cb11 --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/AddressDto.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.auto.value; + +public class AddressDto { + + private String addressLine; + + public AddressDto() { + + } + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } + + public void setAddressLine(String addressLine) { + this.addressLine = addressLine; + } +} diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Person.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Person.java new file mode 100644 index 0000000000..4f71c55c4a --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/Person.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.itest.auto.value; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class Person { + public abstract String getName(); + public abstract int getAge(); + public abstract Address getAddress(); + + public static Builder builder() { + return new AutoValue_Person.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder name(String name); + public abstract Builder age(int age); + public abstract Builder address(Address address); + + public abstract Person build(); + } +} diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonDto.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonDto.java new file mode 100644 index 0000000000..f3e2ecede5 --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonDto.java @@ -0,0 +1,45 @@ +/* + * 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.auto.value; + +public class PersonDto { + private String name; + private int age; + private AddressDto address; + + public PersonDto() { + } + + public PersonDto(String name, int age, AddressDto address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AddressDto getAddress() { + return address; + } + + public void setAddress(AddressDto address) { + this.address = address; + } +} diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonMapper.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonMapper.java new file mode 100644 index 0000000000..9e916c8ad7 --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/main/java/org/mapstruct/itest/auto/value/PersonMapper.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.auto.value; + +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/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 new file mode 100644 index 0000000000..4ce5530a5d --- /dev/null +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.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.auto.value; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for generation of Lombok Builder Mapper implementations + * + * @author Eric Martineau + */ +class AutoValueMapperTest { + + @Test + void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() + .age( 33 ) + .name( "Bob" ) + .address( Address.builder() + .addressLine( "Wild Drive" ) + .build() ) + .build() ); + 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/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml index 6c5b338d2c..cb00fbaa01 100644 --- a/integrationtest/src/test/resources/cdiTest/pom.xml +++ b/integrationtest/src/test/resources/cdiTest/pom.xml @@ -1,22 +1,9 @@ @@ -43,8 +30,8 @@ javax.inject - org.jboss.arquillian.junit - arquillian-junit-container + org.jboss.arquillian.junit5 + arquillian-junit5-container test @@ -58,8 +45,8 @@ test - org.jboss.arquillian.junit - arquillian-junit-core + org.jboss.arquillian.junit5 + arquillian-junit5-core test @@ -69,7 +56,7 @@ org.jboss.weld - weld-core + weld-core-impl test 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 55ee785d37..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 @@ -1,28 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi; -import org.mapstruct.Mapper; import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.cdi.other.DateMapper; -@Mapper( componentModel = "cdi", uses = DateMapper.class ) +@Mapper( componentModel = MappingConstants.ComponentModel.CDI, uses = DateMapper.class ) public interface DecoratedSourceTargetMapper { Target sourceToTarget(Source source); diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Source.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Source.java index a934fccd82..ea2910015a 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Source.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi; diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapper.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapper.java index 684084cace..eb895f5e5a 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapper.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapper.java @@ -1,27 +1,15 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.cdi.other.DateMapper; -@Mapper(componentModel = "cdi", uses = DateMapper.class) +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = DateMapper.class) public interface SourceTargetMapper { Target sourceToTarget(Source source); diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java index 9f12980fbc..8dd673a2e2 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi; diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Target.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Target.java index b97ee87b49..857ab8aaf2 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Target.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi; diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/other/DateMapper.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/other/DateMapper.java index 88a4761e95..a9c4706bd8 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/other/DateMapper.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/other/DateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.cdi.other; 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 4484795e3c..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 @@ -1,43 +1,30 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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; @@ -51,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 ); @@ -67,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/beanjar/pom.xml b/integrationtest/src/test/resources/externalbeanjar/beanjar/pom.xml new file mode 100644 index 0000000000..28ea208e1a --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/beanjar/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + org.mapstruct + externalbeanjar + 1.0.0 + ../pom.xml + + + beanjar + jar + + diff --git a/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Source.java b/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Source.java new file mode 100644 index 0000000000..0e6e956efa --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Source.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.externalbeanjar; + +import java.math.BigDecimal; + +public class Source { + + private BigDecimal bigDecimal; + + public BigDecimal getBigDecimal() { + return bigDecimal; + } + + public void setBigDecimal(BigDecimal bigDecimal) { + this.bigDecimal = bigDecimal; + } +} diff --git a/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Target.java b/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Target.java new file mode 100644 index 0000000000..26c42e5869 --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/beanjar/src/main/java/org/mapstruct/itest/externalbeanjar/Target.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.externalbeanjar; + +public class Target { + + private Integer integer; + + public Integer getInteger() { + return integer; + } + + public void setInteger(Integer integer) { + this.integer = integer; + } +} diff --git a/integrationtest/src/test/resources/externalbeanjar/mapper/pom.xml b/integrationtest/src/test/resources/externalbeanjar/mapper/pom.xml new file mode 100644 index 0000000000..aa5546f587 --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/mapper/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + org.mapstruct + externalbeanjar + 1.0.0 + ../pom.xml + + + mapper + jar + + + + org.mapstruct + beanjar + 1.0.0 + + + 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 new file mode 100644 index 0000000000..35cfacdb89 --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/main/java/org/mapstruct/itest/externalbeanjar/Issue1121Mapper.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 + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mapstruct.itest.externalbeanjar; + +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; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface Issue1121Mapper { + + Issue1121Mapper INSTANCE = Mappers.getMapper( Issue1121Mapper.class ); + + @Mapping(target = "integer", source = "bigDecimal") + Target map(Source source); + +} 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 new file mode 100644 index 0000000000..5d12c1b0f2 --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.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.simple; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; +import org.mapstruct.itest.externalbeanjar.Issue1121Mapper; +import org.mapstruct.itest.externalbeanjar.Source; +import org.mapstruct.itest.externalbeanjar.Target; + +import static org.assertj.core.api.Assertions.assertThat; + +class ConversionTest { + + @Test + void shouldApplyConversions() { + Source source = new Source(); + source.setBigDecimal( new BigDecimal( "42" ) ); + + Target target = Issue1121Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getInteger() ).isEqualTo( 42 ); + } + +} diff --git a/integrationtest/src/test/resources/externalbeanjar/pom.xml b/integrationtest/src/test/resources/externalbeanjar/pom.xml new file mode 100644 index 0000000000..ef3bf4da93 --- /dev/null +++ b/integrationtest/src/test/resources/externalbeanjar/pom.xml @@ -0,0 +1,25 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + externalbeanjar + pom + + beanjar + mapper + + diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml new file mode 100644 index 0000000000..60b73b856c --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-faultyAstModifyingProcessor-aggregator + 1.0.0 + ../pom.xml + + + itest-faultyAstModifyingProcessor-generator + jar + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + + diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/AstModifyingProcessorSaysYes.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/AstModifyingProcessorSaysYes.java new file mode 100644 index 0000000000..e4a5fe5ead --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/AstModifyingProcessorSaysYes.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.faultyAstModifyingProcessor; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class AstModifyingProcessorSaysYes implements AstModifyingAnnotationProcessor { + + @Override + public boolean isTypeComplete(TypeMirror type) { + return true; + } +} diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyAstModifyingProcessor.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyAstModifyingProcessor.java new file mode 100644 index 0000000000..ba1be9b2cc --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyAstModifyingProcessor.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.faultyAstModifyingProcessor; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class FaultyAstModifyingProcessor implements AstModifyingAnnotationProcessor { + + public FaultyAstModifyingProcessor() { + throw new RuntimeException( "Failed to create processor" ); + } + + @Override + public boolean isTypeComplete(TypeMirror type) { + return true; + } +} diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyStaticAstModifyingProcessor.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyStaticAstModifyingProcessor.java new file mode 100644 index 0000000000..5f16165014 --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/FaultyStaticAstModifyingProcessor.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.faultyAstModifyingProcessor; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class FaultyStaticAstModifyingProcessor implements AstModifyingAnnotationProcessor { + + static { + if ( true ) { + throw new RuntimeException( "Failed to initialize class" ); + } + } + + @Override + public boolean isTypeComplete(TypeMirror type) { + return true; + } +} diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor new file mode 100644 index 0000000000..3bc4f6138c --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor @@ -0,0 +1,7 @@ +# Copyright MapStruct Authors. +# +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 +org.mapstruct.itest.faultyAstModifyingProcessor.UnknownAstModifyingProcessor +org.mapstruct.itest.faultyAstModifyingProcessor.AstModifyingProcessorSaysYes +org.mapstruct.itest.faultyAstModifyingProcessor.FaultyAstModifyingProcessor +org.mapstruct.itest.faultyAstModifyingProcessor.FaultyStaticAstModifyingProcessor diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/pom.xml new file mode 100644 index 0000000000..dae561252e --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + org.mapstruct.itest + itest-faultyAstModifyingProcessor-aggregator + pom + + + generator + usage + + diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml new file mode 100644 index 0000000000..976664f88d --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-faultyAstModifyingProcessor-aggregator + 1.0.0 + ../pom.xml + + + itest-faultyAstModifyingProcessor-usage + jar + + + + org.junit.jupiter + junit-jupiter + test + + + org.mapstruct.itest + itest-faultyAstModifyingProcessor-generator + 1.0.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -XprintProcessorInfo + -XprintRounds + + -proc:none + + + + + diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/Order.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/Order.java new file mode 100644 index 0000000000..1d7cd6380b --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/Order.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.faultyAstModifyingProcessor.usage; + +/** + * @author Filip Hrisafov + */ +public class Order { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderDto.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderDto.java new file mode 100644 index 0000000000..afc64606b1 --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderDto.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.faultyAstModifyingProcessor.usage; + +/** + * @author Filip Hrisafov + */ +public class OrderDto { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderMapper.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderMapper.java new file mode 100644 index 0000000000..75e52eb474 --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/main/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/OrderMapper.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.faultyAstModifyingProcessor.usage; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OrderMapper { + + OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); + + OrderDto orderToDto(Order order); +} 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 new file mode 100644 index 0000000000..ed6a5c0d89 --- /dev/null +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.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.faultyAstModifyingProcessor.usage; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for using MapStruct with a faulty AstModifyingProcessor (i.e. when the processor cannot be loaded) + * + * @author Filip Hrisafov + */ +class FaultyAstModifyingTestTest { + + @Test + void testMapping() { + Order order = new Order(); + order.setItem( "my item" ); + + OrderDto dto = OrderMapper.INSTANCE.orderToDto( order ); + assertThat( dto.getItem() ).isEqualTo( "my item" ); + } +} diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/pom.xml b/integrationtest/src/test/resources/freeBuilderBuilderTest/pom.xml new file mode 100644 index 0000000000..9e8c501ec3 --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + freeBuilderIntegrationTest + jar + + + + org.inferred + freebuilder + provided + + + diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Address.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Address.java new file mode 100644 index 0000000000..1b6878fb8c --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Address.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.freebuilder; + +import org.inferred.freebuilder.FreeBuilder; + +@FreeBuilder +public abstract class Address { + public abstract String getAddressLine(); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends Address_Builder { } +} diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/AddressDto.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/AddressDto.java new file mode 100644 index 0000000000..053840b441 --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/AddressDto.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.freebuilder; + +public class AddressDto { + + private String addressLine; + + public AddressDto() { + + } + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } + + public void setAddressLine(String addressLine) { + this.addressLine = addressLine; + } +} diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Person.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Person.java new file mode 100644 index 0000000000..b4d544d67a --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/Person.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.freebuilder; + +import org.inferred.freebuilder.FreeBuilder; + +@FreeBuilder +public abstract class Person { + public abstract String getName(); + public abstract int getAge(); + public abstract Address getAddress(); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends Person_Builder { } +} diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonDto.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonDto.java new file mode 100644 index 0000000000..3b4a17e591 --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonDto.java @@ -0,0 +1,45 @@ +/* + * 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.freebuilder; + +public class PersonDto { + private String name; + private int age; + private AddressDto address; + + public PersonDto() { + } + + public PersonDto(String name, int age, AddressDto address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AddressDto getAddress() { + return address; + } + + public void setAddress(AddressDto address) { + this.address = address; + } +} diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonMapper.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonMapper.java new file mode 100644 index 0000000000..2b732271a3 --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/main/java/org/mapstruct/itest/freebuilder/PersonMapper.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.freebuilder; + +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/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 new file mode 100644 index 0000000000..3b1caebf9c --- /dev/null +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.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.freebuilder; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for generation of Lombok Builder Mapper implementations + * + * @author Eric Martineau + */ +class FreeBuilderMapperTest { + + @Test + void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() + .setAge( 33 ) + .setName( "Bob" ) + .setAddress( Address.builder() + .setAddressLine( "Wild Drive" ) + .build() ) + .build() ); + 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/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index ac3950062d..670d5cff92 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -1,22 +1,9 @@ @@ -37,6 +24,14 @@ x x x + x + x + x + x + x + x + x + x @@ -52,10 +47,21 @@ **/*Test.java **/testutil/**/*.java **/spi/**/*.java + **/kotlin/**/*.java ${additionalExclude0} ${additionalExclude1} ${additionalExclude2} ${additionalExclude3} + ${additionalExclude4} + ${additionalExclude5} + ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} @@ -71,6 +77,14 @@ javax.inject javax.inject + + jakarta.inject + jakarta.inject-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + @@ -86,5 +100,35 @@ joda-time joda-time + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + true + + + org.glassfish.jaxb + jaxb-runtime + 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 26bd96caed..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 @@ -1,40 +1,26 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 ); @@ -47,19 +33,7 @@ public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { } @Test - public void shouldNotPropagateIgnoredPropertyInReverseMappingWhenNameIsSame() { - AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23, "black" ); - - Animal animal = AnimalMapper.INSTANCE.animalDtoToAnimal( animalDto ); - - assertThat( animal ).isNotNull(); - assertThat( animalDto.getName() ).isEqualTo( "Bruno" ); - assertThat( animalDto.getSize() ).isEqualTo( 100 ); - assertThat( animal.getAge() ).isNull(); - } - - @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 new file mode 100644 index 0000000000..0df032f0f2 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +plugins { + id 'java' +} + +if (!project.hasProperty('mapstructRootPath')) + throw new IllegalArgumentException("Missing required property: mapstructRootPath") + +repositories { + jcenter() + mavenLocal() + flatDir { + dirs "${mapstructRootPath}/processor/target" + dirs "${mapstructRootPath}/core/target" + } +} + +// Extract version and artifactId values +def apPom = new XmlParser().parse(file("${mapstructRootPath}/processor/pom.xml")) +ext.apArtifactId = apPom.artifactId[0].text() +ext.apVersion = apPom.parent[0].version[0].text() + +def libPom = new XmlParser().parse(file("${mapstructRootPath}/core/pom.xml")) +ext.libArtifactId = libPom.artifactId[0].text() +ext.libVersion = libPom.parent[0].version[0].text() + +dependencies { + annotationProcessor name: "${apArtifactId}-${apVersion}" + implementation name: "${libArtifactId}-${libVersion}" +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle new file mode 100644 index 0000000000..b6ca918e91 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle @@ -0,0 +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 new file mode 100644 index 0000000000..52c64051f1 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/TestMapper.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.gradle.lib; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.itest.gradle.model.Source; +import org.mapstruct.itest.gradle.model.Target; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface TestMapper { + @Mapping(target = "field", source = "value") + public Target toTarget(Source source); +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java new file mode 100644 index 0000000000..9f1095850c --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/UnrelatedComponent.java @@ -0,0 +1,12 @@ +/* + * 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.gradle.lib; + +public class UnrelatedComponent { + public boolean unrelatedMethod() { + return true; + } +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.java new file mode 100644 index 0000000000..c7103aaceb --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Source.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.gradle.model; + +public class Source { + private int value; + + public void setValue(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.java b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.java new file mode 100644 index 0000000000..4b5a171109 --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/model/Target.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.gradle.model; + +public class Target { + private String field = getDefaultValue(); + private String otherField; + + public void setField(String field) { + this.field = field; + } + + public String getField() { + return field; + } + + public void setOtherField(String otherField) { + this.otherField = otherField; + } + + public String getOtherField() { + return otherField; + } + + public String getDefaultValue() { + return "original"; + } +} diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/extras/pom.xml b/integrationtest/src/test/resources/immutablesBuilderTest/extras/pom.xml new file mode 100644 index 0000000000..5141f2998b --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/extras/pom.xml @@ -0,0 +1,31 @@ + + + + 4.0.0 + + + immutablesIntegrationTest + org.mapstruct + 1.0.0 + + + itest-immutables-mapping-extras + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + + + + \ No newline at end of file diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/pom.xml b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/pom.xml new file mode 100644 index 0000000000..10c481b66e --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + immutablesIntegrationTest + org.mapstruct + 1.0.0 + + + itest-immutables-mapper + + + + + org.immutables + value + provided + + + org.mapstruct + itest-immutables-mapping-extras + 1.0.0 + + + \ No newline at end of file diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Address.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Address.java new file mode 100644 index 0000000000..ce9c033ad0 --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/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.immutables; + +import org.immutables.value.Value; + +@Value.Immutable +public interface Address { + String getAddressLine(); +} diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/AddressDto.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/AddressDto.java new file mode 100644 index 0000000000..0f24e182f5 --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/AddressDto.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.immutables; + +public class AddressDto { + + private String addressLine; + + public AddressDto() { + + } + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } + + public void setAddressLine(String addressLine) { + this.addressLine = addressLine; + } +} diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Person.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Person.java new file mode 100644 index 0000000000..dfbfc47b74 --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Person.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.immutables; + +import org.immutables.value.Value; + +@Value.Immutable +public interface Person { + String getName(); + int getAge(); + Address getAddress(); +} diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonDto.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonDto.java new file mode 100644 index 0000000000..44a6b7b9e3 --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonDto.java @@ -0,0 +1,45 @@ +/* + * 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.immutables; + +public class PersonDto { + private String name; + private int age; + private AddressDto address; + + public PersonDto() { + } + + public PersonDto(String name, int age, AddressDto address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AddressDto getAddress() { + return address; + } + + public void setAddress(AddressDto address) { + this.address = address; + } +} diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonMapper.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonMapper.java new file mode 100644 index 0000000000..6023833ec8 --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/PersonMapper.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.immutables; + +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/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 new file mode 100644 index 0000000000..9990f4278c --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.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.immutables; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for generation of Lombok Builder Mapper implementations + * + * @author Eric Martineau + */ +class ImmutablesMapperTest { + + @Test + void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( ImmutablePerson.builder() + .age( 33 ) + .name( "Bob" ) + .address( ImmutableAddress.builder() + .addressLine( "Wild Drive" ) + .build() ) + .build() ); + 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/immutablesBuilderTest/pom.xml b/integrationtest/src/test/resources/immutablesBuilderTest/pom.xml new file mode 100644 index 0000000000..51542adbcb --- /dev/null +++ b/integrationtest/src/test/resources/immutablesBuilderTest/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + immutablesIntegrationTest + pom + + + extras + mapper + + 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/pom.xml b/integrationtest/src/test/resources/java8Test/pom.xml index 1502c49085..face2f4564 100644 --- a/integrationtest/src/test/resources/java8Test/pom.xml +++ b/integrationtest/src/test/resources/java8Test/pom.xml @@ -1,22 +1,9 @@ diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Source.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Source.java index f1855e168a..76925c4430 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Source.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._603; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapper.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapper.java index e3bc6ea958..129c459816 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapper.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._603; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapperDecorator.java index 8b79635edb..7d47b1f836 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/SourceTargetMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._603; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Target.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Target.java index 3fcf7894cf..d1511d7fdb 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Target.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_603/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._603; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Bar.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Bar.java index 23164eb6ce..54a360f7e3 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Bar.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Bar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Foo.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Foo.java index 8ff588c32d..80c01d4fd6 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Foo.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Foo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Source.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Source.java index 41598c40f2..e7a44792e9 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Source.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetBaseMapper.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetBaseMapper.java index 43fc434f92..2b09b1e59e 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetBaseMapper.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetBaseMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; @@ -23,6 +10,11 @@ @Mapper public interface SourceTargetBaseMapper { - // TODO.. move default and static interface method here when problem in eclipse processor is fixed. + default Foo fooFromId(long id) { + return new Foo(id); + } + static Bar barFromId(String id) { + return new Bar(id); + } } diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetMapper.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetMapper.java index 1443b90c77..85102e1918 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetMapper.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; @@ -28,17 +15,8 @@ public interface SourceTargetMapper extends SourceTargetBaseMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings({ - @Mapping(source = "idFoo", target = "foo"), - @Mapping(source = "idBar", target = "bar") + @Mapping(target = "foo", source = "idFoo"), + @Mapping(target = "bar", source = "idBar") }) Target mapSourceToTarget(Source source); - - default Foo fooFromId(long id) { - return new Foo(id); - } - - static Bar barFromId(String id) { - return new Bar(id); - } - } diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Target.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Target.java index 0374c69183..5a45d57d1f 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Target.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/ap/test/bugs/_636/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.test.bugs._636; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Java8Mapper.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Java8Mapper.java index 062226b020..f16228b60e 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Java8Mapper.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Java8Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.java8; @@ -27,7 +14,7 @@ public interface Java8Mapper { Java8Mapper INSTANCE = Mappers.getMapper( Java8Mapper.class ); - @Mapping(source = "firstName", target = "givenName") - @Mapping(source = "lastName", target = "surname") + @Mapping(target = "givenName", source = "firstName") + @Mapping(target = "surname", source = "lastName") Target sourceToTarget(Source source); } diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Source.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Source.java index 7c61b57546..4f25c4a89b 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Source.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.java8; diff --git a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Target.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Target.java index fc98bb5511..d738aa37ac 100644 --- a/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Target.java +++ b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.java8; 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 431626aba8..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 @@ -1,32 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 82397d311e..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 @@ -1,31 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 7b9b0aaa7b..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 @@ -1,36 +1,23 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 776b9e8278..0e69e23e01 100644 --- a/integrationtest/src/test/resources/jaxbTest/pom.xml +++ b/integrationtest/src/test/resources/jaxbTest/pom.xml @@ -1,22 +1,9 @@ true 2.1 + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + true + + + org.glassfish.jaxb + jaxb-runtime + provided + true + + + + + diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/JaxbMapper.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/JaxbMapper.java deleted file mode 100644 index e8eff62500..0000000000 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/JaxbMapper.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.mapstruct.itest.jaxb; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.bind.JAXBElement; - -import org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory; - -/** - * This class can be removed as soon as MapStruct is capable of generating List mappings. - * - * @author Sjaak Derksen - */ -public class JaxbMapper { - - private final ObjectFactory of = new ObjectFactory(); - - /** - * This method is needed, because currently MapStruct is not capable of selecting - * the proper factory method for Lists - * - * @param orderDetailsDescriptions - * - * @return - */ - List> toJaxbList(List orderDetailsDescriptions) { - - List> result = new ArrayList>(); - for ( String orderDetailDescription : orderDetailsDescriptions ) { - result.add( of.createOrderDetailsTypeDescription( orderDetailDescription ) ); - } - return result; - } - -} diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDetailsDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDetailsDto.java index c259845bf4..9350344d56 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDetailsDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDetailsDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDto.java index 859f7afc44..e8d268a7ac 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderStatusDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderStatusDto.java index ef8adce58a..df87932d73 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderStatusDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/OrderStatusDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/ShippingAddressDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/ShippingAddressDto.java index 2108793306..6c2b033c5b 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/ShippingAddressDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/ShippingAddressDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; 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 5b63de01a4..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 @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; @@ -26,15 +13,13 @@ import org.mapstruct.itest.jaxb.xsd.test2.ShippingAddressType; import org.mapstruct.itest.jaxb.xsd.underscores.SubType; - /** * @author Sjaak Derksen */ @Mapper(uses = { org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory.class, org.mapstruct.itest.jaxb.xsd.test2.ObjectFactory.class, - org.mapstruct.itest.jaxb.xsd.underscores.ObjectFactory.class, - JaxbMapper.class + org.mapstruct.itest.jaxb.xsd.underscores.ObjectFactory.class }) public interface SourceTargetMapper { diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SubTypeDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SubTypeDto.java index 309c40a409..affa8e3536 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SubTypeDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SubTypeDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SuperTypeDto.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SuperTypeDto.java index 9eb16cbf28..f07875e8ee 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SuperTypeDto.java +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SuperTypeDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jaxb; diff --git a/integrationtest/src/test/resources/jaxbTest/src/main/resources/binding/binding.xjb b/integrationtest/src/test/resources/jaxbTest/src/main/resources/binding/binding.xjb index 0255b11c6a..3d5129c5e4 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/main/resources/binding/binding.xjb +++ b/integrationtest/src/test/resources/jaxbTest/src/main/resources/binding/binding.xjb @@ -1,22 +1,9 @@ @@ -31,6 +18,10 @@ jsr330Test jar + + + ${org.junit6.jupiter.version} + @@ -47,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 3df11f28d6..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 @@ -1,28 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330; -import org.mapstruct.Mapper; import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.jsr330.other.DateMapper; -@Mapper(componentModel = "jsr330", uses = DateMapper.class) +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = DateMapper.class) @DecoratedWith(SourceTargetMapperDecorator.class) public interface DecoratedSourceTargetMapper { 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 6cc0ae028f..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 @@ -1,28 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330; -import org.mapstruct.Mapper; import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.jsr330.other.DateMapper; -@Mapper(componentModel = "jsr330", uses = DateMapper.class) +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = DateMapper.class) @DecoratedWith(SecondSourceTargetMapperDecorator.class) public interface SecondDecoratedSourceTargetMapper { 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 cbc1c13894..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 @@ -1,25 +1,12 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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/Source.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Source.java index 629a9396e7..6edf1e5133 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Source.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330; diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java index 3dc8e69078..a96a860da5 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java @@ -1,27 +1,15 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.jsr330.other.DateMapper; -@Mapper(componentModel = "jsr330", uses = DateMapper.class) +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = DateMapper.class) public interface SourceTargetMapper { Target sourceToTarget(Source source); 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 c1b47381c6..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 @@ -1,25 +1,12 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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/Target.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Target.java index fd9ec3a779..b3efb96456 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Target.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330; 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 895aa4c417..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 @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.jsr330.other; @@ -22,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 d6c0f75ca3..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 @@ -1,33 +1,19 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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; @@ -37,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 { @@ -56,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 ); @@ -67,7 +53,7 @@ public void shouldInjectJsr330BasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -84,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 new file mode 100644 index 0000000000..f86f92b9c4 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinDataTest/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + kotlinDataTest + jar + + + 1.6.0 + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + + + generate-via-compiler-plugin + + false + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + + \${project.basedir}/src/main/kotlin + \${project.basedir}/src/main/java + + + + + test-compile + test-compile + + + \${project.basedir}/src/test/kotlin + \${project.basedir}/src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + compile + + + java-test-compile + test-compile + testCompile + + + + + + + + + diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.java b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.java new file mode 100644 index 0000000000..e992eb0e60 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerEntity.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.kotlin.data; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.java b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.java new file mode 100644 index 0000000000..b2157252db --- /dev/null +++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/java/org/mapstruct/itest/kotlin/data/CustomerMapper.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.kotlin.data; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromRecord(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toRecord(CustomerEntity entity); + +} diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt b/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt new file mode 100644 index 0000000000..820a8b56c3 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinDataTest/src/main/kotlin/org/mapstruct/itest/kotlin/data/CustomerDto.kt @@ -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.kotlin.data; + +data class CustomerDto(var name: String?, var email: String?) { + +} 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 new file mode 100644 index 0000000000..39bb909746 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java @@ -0,0 +1,38 @@ +/* + * 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.kotlin.data; + +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; + +import static org.assertj.core.api.Assertions.assertThat; + +class KotlinDataTest { + + @Test + void shouldMapData() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @Test + void shouldMapIntoData() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toRecord( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getEmail() ).isEqualTo( "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/pom.xml b/integrationtest/src/test/resources/lombokBuilderTest/pom.xml new file mode 100644 index 0000000000..5d43efb1d3 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + lombokIntegrationTest + jar + + + + org.projectlombok + lombok + compile + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + compile + + + diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Address.java b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Address.java new file mode 100644 index 0000000000..248587a308 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Address.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.lombok; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class Address { + private final String addressLine; +} diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java new file mode 100644 index 0000000000..36c71607b6 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.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; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AddressDto { + + private String addressLine; +} diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Person.java b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Person.java new file mode 100644 index 0000000000..fcfaa6f49f --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Person.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.lombok; + +import lombok.Builder; +import lombok.Getter; + +@Builder(builderMethodName = "foo", buildMethodName = "create", builderClassName = "Builder") +@Getter +public class Person { + private final String name; + private final int age; + private final Address address; +} diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java new file mode 100644 index 0000000000..f5607d8b74 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.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; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PersonDto { + private String name; + private int age; + private AddressDto address; +} 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 new file mode 100644 index 0000000000..060fb5a990 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -0,0 +1,43 @@ +/* + * 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( Person.foo() + .age( 33 ) + .name( "Bob" ) + .address( Address.builder() + .addressLine( "Wild Drive" ) + .build() ) + .create() ); + 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/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/PersonMapper.java b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/PersonMapper.java new file mode 100644 index 0000000000..a7185d5011 --- /dev/null +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/PersonMapper.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.lombok; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * The Builder creation method is not present during the annotation processing of the mapper. + * Therefore we put the mapper into a separate class. + * + * @see Lombok Issue #1538 + */ +@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/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/pom.xml b/integrationtest/src/test/resources/moduleInfoTest/pom.xml new file mode 100644 index 0000000000..4561a1b69e --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/pom.xml @@ -0,0 +1,24 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + modulesTest + jar + + diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/main/java/module-info.java b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/module-info.java new file mode 100644 index 0000000000..07b4a62b06 --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/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 test.mapstruct { + requires org.mapstruct; + exports org.mapstruct.itest.modules; +} diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerDto.java b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerDto.java new file mode 100644 index 0000000000..30ebb32968 --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerDto.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.modules; + +/** + * @author Filip Hrisafov + */ +public class CustomerDto { + + private String name; + private String email; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerEntity.java b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerEntity.java new file mode 100644 index 0000000000..a52a887f08 --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerEntity.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.modules; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerMapper.java b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerMapper.java new file mode 100644 index 0000000000..119bffaae8 --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/src/main/java/org/mapstruct/itest/modules/CustomerMapper.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.itest.modules; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromDto(CustomerDto record); + +} 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 new file mode 100644 index 0000000000..d54c7e6572 --- /dev/null +++ b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.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.itest.modules; + +import org.junit.jupiter.api.Test; +import org.mapstruct.itest.modules.CustomerDto; +import org.mapstruct.itest.modules.CustomerEntity; +import org.mapstruct.itest.modules.CustomerMapper; + +import static org.assertj.core.api.Assertions.assertThat; + +class ModulesTest { + + @Test + void shouldMapRecord() { + CustomerDto dto = new CustomerDto(); + dto.setName( "Kermit" ); + dto.setEmail( "kermit@test.com" ); + CustomerEntity customer = CustomerMapper.INSTANCE.fromDto( dto ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } +} diff --git a/integrationtest/src/test/resources/namingStrategyTest/pom.xml b/integrationtest/src/test/resources/namingStrategyTest/pom.xml index ff0b80c195..c337167b53 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/pom.xml +++ b/integrationtest/src/test/resources/namingStrategyTest/pom.xml @@ -1,22 +1,9 @@ diff --git a/integrationtest/src/test/resources/namingStrategyTest/strategy/pom.xml b/integrationtest/src/test/resources/namingStrategyTest/strategy/pom.xml index 890cbfde94..6045742e36 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/strategy/pom.xml +++ b/integrationtest/src/test/resources/namingStrategyTest/strategy/pom.xml @@ -1,22 +1,9 @@ @@ -40,4 +27,17 @@ provided + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + diff --git a/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java b/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java index 7d8cb9acc0..eb9d07a246 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java +++ b/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java @@ -1,30 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.naming; -import java.beans.Introspector; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; +import org.mapstruct.ap.spi.util.IntrospectorUtils; /** * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the @@ -55,13 +41,14 @@ public boolean isAdderMethod(ExecutableElement method) { @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); + return IntrospectorUtils.decapitalize( + methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); } @Override public String getElementName(ExecutableElement adderMethod) { String methodName = adderMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); } } diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml b/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml index 6d9c323921..319cefd218 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml @@ -1,22 +1,9 @@ diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayer.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayer.java index 8d7e0c0cd5..0536f5ee01 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayer.java +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayer.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.naming; diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerDto.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerDto.java index 082ed3fbfd..62e9893ce2 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerDto.java +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.naming; diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerMapper.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerMapper.java index e4c57ca21c..07dcf00b76 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerMapper.java +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/main/java/org/mapstruct/itest/naming/GolfPlayerMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.naming; 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 515cb14df3..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 @@ -1,39 +1,26 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 bad5032cbd..f4cd0b51de 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -1,22 +1,9 @@ ${mapstruct.version} - mapstruct - - - - 1.7.1 + 6.0.3 @@ -82,35 +65,31 @@ - generate-via-processor-plugin + generate-via-compiler-plugin-with-annotation-processor-paths false - org.bsc.maven - maven-processor-plugin - - - process - generate-sources - - process - - - + org.apache.maven.plugins + maven-compiler-plugin - \${project.build.directory}/generated-sources/mapstruct - - org.mapstruct.ap.MappingProcessor - + + \${compiler-id} + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + + - ${project.groupId} - mapstruct-processor - ${mapstruct.version} + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tycho.compiler-jdt.version} @@ -150,15 +129,15 @@ ${project.groupId} - \${mapstruct-artifact-id} + mapstruct ${mapstruct.version} provided - junit - junit + org.junit.jupiter + junit-jupiter test @@ -178,19 +157,6 @@ \${compiler-source-target-version} - - org.apache.maven.plugins - maven-toolchains-plugin - 1.0 - - - - \${toolchain-jdk-version} - \${toolchain-jdk-vendor} - - - - org.apache.maven.plugins maven-enforcer-plugin diff --git a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml new file mode 100644 index 0000000000..c0d8e2a81b --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + protobufIntegrationTest + jar + + + 1.6.0 + 0.6.1 + + + + + com.google.protobuf + protobuf-java + provided + + + + + + + kr.motd.maven + os-maven-plugin + ${os.mavenplugin.version} + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf.mavenplugin.version} + + + generate-sources + + compile + compile-custom + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} + + + + + + + + diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.java new file mode 100644 index 0000000000..0956722de8 --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/AddressDto.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.protobuf; + +public class AddressDto { + + private String addressLine; + + public AddressDto() { + + } + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } + + public void setAddressLine(String addressLine) { + this.addressLine = addressLine; + } +} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java new file mode 100644 index 0000000000..61ee37b391 --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonDto.java @@ -0,0 +1,45 @@ +/* + * 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.protobuf; + +public class PersonDto { + private String name; + private int age; + private AddressDto address; + + public PersonDto() { + } + + public PersonDto(String name, int age, AddressDto address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public AddressDto getAddress() { + return address; + } + + public void setAddress(AddressDto address) { + this.address = address; + } +} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.java new file mode 100644 index 0000000000..b5cdc8575a --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/main/java/org/mapstruct/itest/protobuf/PersonMapper.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.protobuf; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) // protobuf has a lot of strange additional setters/getters +public interface PersonMapper { + + PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); + + PersonProtos.Person fromDto(PersonDto personDto); + PersonDto toDto(PersonProtos.Person personDto); +} diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/main/proto/Person.proto b/integrationtest/src/test/resources/protobufBuilderTest/src/main/proto/Person.proto new file mode 100644 index 0000000000..209c2220b8 --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/main/proto/Person.proto @@ -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 + */ +syntax = "proto3"; + +package itest.protobuf; + +option java_package = "org.mapstruct.itest.protobuf"; +option java_outer_classname = "PersonProtos"; + +message Person { + string name = 1; + int32 age = 2; + Address address = 3; + + message Address { + string addressLine = 1; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..ac97eba8b4 --- /dev/null +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -0,0 +1,44 @@ +/* + * 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.protobuf; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for generation of Protobuf Builder Mapper implementations + * + * @author Christian Bandowski + */ +class ProtobufMapperTest { + + @Test + void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder() + .setAge( 33 ) + .setName( "Bob" ) + .setAddress( PersonProtos.Person.Address.newBuilder() + .setAddressLine( "Wild Drive" ) + .build() ) + .build() ); + + assertThat( personDto.getAge() ).isEqualTo( 33 ); + assertThat( personDto.getName() ).isEqualTo( "Bob" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); + } + + @Test + void testLombokToImmutable() { + PersonProtos.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/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/api/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleTest/api/pom.xml new file mode 100644 index 0000000000..362f2d3840 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/api/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + recordsCrossModuleTest + org.mapstruct + 1.0.0 + + + records-cross-module-api + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/api/src/main/java/org/mapstruct/itest/records/api/CustomerDto.java b/integrationtest/src/test/resources/recordsCrossModuleTest/api/src/main/java/org/mapstruct/itest/records/api/CustomerDto.java new file mode 100644 index 0000000000..344aa79d96 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/api/src/main/java/org/mapstruct/itest/records/api/CustomerDto.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.api; + +/** + * @author Filip Hrisafov + */ +public record CustomerDto(String name, String email) { + +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/pom.xml new file mode 100644 index 0000000000..6fc250ed45 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + + recordsCrossModuleTest + org.mapstruct + 1.0.0 + + + records-cross-module-mapper + + + + + org.mapstruct + records-cross-module-api + 1.0.0 + + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerEntity.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerEntity.java new file mode 100644 index 0000000000..51bcdaa8e1 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerEntity.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.mapper; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerMapper.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerMapper.java new file mode 100644 index 0000000000..7b679a4245 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/main/java/org/mapstruct/itest/records/mapper/CustomerMapper.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.itest.records.mapper; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; +import org.mapstruct.itest.records.api.CustomerDto; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromRecord(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toRecord(CustomerEntity entity); + +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/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 new file mode 100644 index 0000000000..49c73f2f9c --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java @@ -0,0 +1,39 @@ +/* + * 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.mapper; + +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; + +import static org.assertj.core.api.Assertions.assertThat; + +class RecordsTest { + + @Test + void shouldMapRecord() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @Test + void shouldMapIntoRecord() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toRecord( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.name() ).isEqualTo( "Kermit" ); + assertThat( customer.email() ).isEqualTo( "kermit@test.com" ); + } + +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleTest/pom.xml new file mode 100644 index 0000000000..47c55ea410 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + recordsCrossModuleTest + pom + + + api + mapper + + diff --git a/integrationtest/src/test/resources/recordsTest/pom.xml b/integrationtest/src/test/resources/recordsTest/pom.xml new file mode 100644 index 0000000000..c74469a7ba --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + recordsTest + 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/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/CustomerDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerDto.java new file mode 100644 index 0000000000..69b2547bc3 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerDto.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 CustomerDto(String name, String email) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerEntity.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerEntity.java new file mode 100644 index 0000000000..1663bbac5a --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerEntity.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 CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java new file mode 100644 index 0000000000..addb8c5906 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.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.records; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromRecord(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toRecord(CustomerEntity entity); + + @Mapping(target = "value", source = "name") + GenericRecord toValue(CustomerEntity entity); + +} 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/GenericRecord.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/GenericRecord.java new file mode 100644 index 0000000000..9a39b59968 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/GenericRecord.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 GenericRecord(T value) { + +} 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 new file mode 100644 index 0000000000..0960c92a45 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -0,0 +1,98 @@ +/* + * 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.Arrays; + +import org.junit.jupiter.api.Test; +import org.mapstruct.itest.records.CustomerDto; +import org.mapstruct.itest.records.CustomerEntity; +import org.mapstruct.itest.records.CustomerMapper; + +import static org.assertj.core.api.Assertions.assertThat; + +class RecordsTest { + + @Test + void shouldMapRecord() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @Test + void shouldMapIntoRecord() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toRecord( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.name() ).isEqualTo( "Kermit" ); + assertThat( customer.email() ).isEqualTo( "kermit@test.com" ); + } + + @Test + void shouldMapIntoGenericRecord() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + GenericRecord value = CustomerMapper.INSTANCE.toValue( entity ); + + 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/pom.xml b/integrationtest/src/test/resources/simpleTest/pom.xml index 47ca457da8..1bcfe99dcb 100644 --- a/integrationtest/src/test/resources/simpleTest/pom.xml +++ b/integrationtest/src/test/resources/simpleTest/pom.xml @@ -1,22 +1,9 @@ diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/BaseType.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/BaseType.java index 0ac10af39a..1613106a38 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/BaseType.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/BaseType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/ReferencedCustomMapper.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/ReferencedCustomMapper.java index d73e73895b..85d551f091 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/ReferencedCustomMapper.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/ReferencedCustomMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeOtherType.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeOtherType.java index d6ec49dd99..cfa55535d9 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeOtherType.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeOtherType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeType.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeType.java index 1d3f0e3d18..2bf7d3454a 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeType.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SomeType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Source.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Source.java index f48e9e0913..48093144cb 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Source.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java index 99d2ffe655..dd893384f7 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; @@ -29,9 +16,9 @@ public abstract class SourceTargetAbstractMapper { public static SourceTargetAbstractMapper INSTANCE = Mappers.getMapper( SourceTargetAbstractMapper.class ); @Mappings({ - @Mapping(source = "qax", target = "baz"), - @Mapping(source = "baz", target = "qax"), - @Mapping(source = "forNested.value", target = "fromNested") + @Mapping(target = "baz", source = "qax"), + @Mapping(target = "qax", source = "baz"), + @Mapping(target = "fromNested", source = "forNested.value") }) public abstract Target sourceToTarget(Source source); 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 62c317cd29..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 @@ -1,27 +1,14 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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) @@ -30,9 +17,9 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings({ - @Mapping(source = "qax", target = "baz"), - @Mapping(source = "baz", target = "qax"), - @Mapping(source = "forNested.value", target = "fromNested") + @Mapping(target = "baz", source = "qax"), + @Mapping(target = "qax", source = "baz"), + @Mapping(target = "fromNested", source = "forNested.value") }) Target sourceToTarget(Source source); 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 6a811c20cf..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 @@ -1,24 +1,12 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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/main/java/org/mapstruct/itest/simple/YetAnotherType.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/YetAnotherType.java index 243079f09f..c79d55aef7 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/YetAnotherType.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/YetAnotherType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.simple; 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 7f759cb3b0..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 @@ -1,34 +1,21 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 ); @@ -43,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 ); @@ -67,7 +54,7 @@ public void shouldApplyConversionsToMappedProperties() { } @Test - public void shouldApplyConversionsForReverseMapping() { + void shouldApplyConversionsForReverseMapping() { Target target = new Target(); target.setFoo( 42L ); target.setBar( 23 ); @@ -82,7 +69,7 @@ public void shouldApplyConversionsForReverseMapping() { } @Test - public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { + void shouldApplyConversionsToMappedPropertiesForReverseMapping() { Target target = new Target(); target.setQax( 42 ); target.setBaz( 23L ); @@ -95,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 7fdab2d384..e4210c0cba 100644 --- a/integrationtest/src/test/resources/springTest/pom.xml +++ b/integrationtest/src/test/resources/springTest/pom.xml @@ -1,22 +1,9 @@ @@ -31,6 +18,10 @@ springTest jar + + + ${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 19d6b5e8ac..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 @@ -1,28 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; -import org.mapstruct.Mapper; import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.spring.other.DateMapper; -@Mapper( componentModel = "spring", uses = DateMapper.class ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = DateMapper.class ) @DecoratedWith( SourceTargetMapperDecorator.class ) public interface DecoratedSourceTargetMapper { 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 2b26d7672f..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 @@ -1,28 +1,16 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; -import org.mapstruct.Mapper; import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.spring.other.DateMapper; -@Mapper( componentModel = "spring", uses = DateMapper.class ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = DateMapper.class ) @DecoratedWith( SecondSourceTargetMapperDecorator.class ) public interface SecondDecoratedSourceTargetMapper { diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java index bd874d685f..3d710102e3 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Source.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Source.java index 725307e0d0..b1d1a2850f 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Source.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapper.java index 25de0cb23a..e6d6b46339 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapper.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapper.java @@ -1,27 +1,15 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.itest.spring.other.DateMapper; -@Mapper(componentModel = "spring", uses = DateMapper.class) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = DateMapper.class) public interface SourceTargetMapper { Target sourceToTarget(Source source); diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java index 8b7031790b..cb487f52cc 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Target.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Target.java index ea95bd3ce5..762c778001 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Target.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring; diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/other/DateMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/other/DateMapper.java index bfcb3a3db5..bb9685e6cb 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/other/DateMapper.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/other/DateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. +/* + * Copyright MapStruct Authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.spring.other; 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 99dbb925d7..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 @@ -1,31 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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; @@ -35,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) @@ -53,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 ); @@ -64,7 +51,7 @@ public void shouldInjectSpringBasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -81,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 9e72814831..f122047093 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -1,3 +1,10 @@ + 4.0.0 @@ -14,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test @@ -25,10 +32,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 - 1.6 - 1.6 -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 c9ce937ae7..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 @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration; 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/generator/src/main/java/org/mapstruct/itest/supertypegeneration/GenBase.java b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/GenBase.java index e43ea14e7f..b5bada8d14 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/GenBase.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/GenBase.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration; diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor index ba0877adfd..ac02411c1c 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,17 +1,4 @@ -# Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) -# and/or other contributors as indicated by the @authors tag. See the -# copyright.txt file in the distribution for a full listing of all -# contributors. +# Copyright MapStruct Authors. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 org.mapstruct.itest.supertypegeneration.BaseGenerationProcessor \ No newline at end of file diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/pom.xml index 7b97cfad3b..e522d12b5b 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/pom.xml @@ -1,22 +1,9 @@ diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index 3f2d54ec6c..4c5e4611f0 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -1,3 +1,10 @@ + 4.0.0 @@ -14,9 +21,8 @@ - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter test @@ -32,10 +38,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 - 1.6 - 1.6 -XprintProcessorInfo -XprintRounds diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/AnotherOrderMapper.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/AnotherOrderMapper.java index 42e829a1e6..09abaf5615 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/AnotherOrderMapper.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/AnotherOrderMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration.usage; diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/Order.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/Order.java index 5b250b1ce7..ae2315bb19 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/Order.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/Order.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration.usage; diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderDto.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderDto.java index a20d639fee..f70224b753 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderDto.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration.usage; diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderMapper.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderMapper.java index 006632766c..b3d6325d74 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderMapper.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.itest.supertypegeneration.usage; 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 6f1f6b4d15..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 @@ -1,40 +1,27 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ 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 new file mode 100644 index 0000000000..89bd9c2d2f --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-targettypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-targettypegeneration-generator + jar + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -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 new file mode 100644 index 0000000000..1880055ba2 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/targettypegeneration/DtoGenerationProcessor.java @@ -0,0 +1,66 @@ +/* + * 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.targettypegeneration; + +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; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; + +/** + * Generates a DTO. + * + * @author Gunnar Morling + * + */ +@SupportedAnnotationTypes("*") +public class DtoGenerationProcessor extends AbstractProcessor { + + private boolean hasRun = false; + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if ( !hasRun ) { + try { + JavaFileObject dto = processingEnv.getFiler().createSourceFile( "org.mapstruct.itest.targettypegeneration.usage.OrderDto" ); + Writer writer = dto.openWriter(); + + writer.append( "package org.mapstruct.itest.targettypegeneration.usage;" ); + writer.append( "\n" ); + writer.append( "public class OrderDto {" ); + writer.append( "\n" ); + writer.append( " private String item;" ); + writer.append( "\n" ); + writer.append( " public String getItem() {" ); + writer.append( " return item;" ); + writer.append( " }" ); + writer.append( "\n" ); + writer.append( " public void setItem(String item) {" ); + writer.append( " this.item = item;" ); + writer.append( " }" ); + writer.append( "}" ); + + writer.flush(); + writer.close(); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + + hasRun = true; + } + + return false; + } +} diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..732c6b2124 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,4 @@ +# Copyright MapStruct Authors. +# +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 +org.mapstruct.itest.targettypegeneration.DtoGenerationProcessor diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/pom.xml new file mode 100644 index 0000000000..ba19d31a71 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + org.mapstruct.itest + itest-targettypegeneration-aggregator + pom + + + generator + usage + + diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml new file mode 100644 index 0000000000..0726580592 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-targettypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-targettypegeneration-usage + jar + + + + org.junit.jupiter + junit-jupiter + test + + + org.mapstruct.itest + itest-targettypegeneration-generator + 1.0.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -XprintProcessorInfo + -XprintRounds + + + + + + diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/Order.java b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/Order.java new file mode 100644 index 0000000000..9c5114675f --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/Order.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.targettypegeneration.usage; + +/** + * @author Gunnar Morling + */ +public class Order { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/OrderMapper.java b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/OrderMapper.java new file mode 100644 index 0000000000..45f8c223f9 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/targettypegeneration/usage/OrderMapper.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.targettypegeneration.usage; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Gunnar Morling + */ +@Mapper +public abstract class OrderMapper { + + public static final OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); + + public abstract OrderDto orderToDto(Order order); +} 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 new file mode 100644 index 0000000000..b2e30a4797 --- /dev/null +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.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.itest.targettypegeneration.usage; + +import org.junit.jupiter.api.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 + * method. + * + * @author Gunnar Morling + */ +class GeneratedTargetTypeTest { + + @Test + void considersPropertiesOnGeneratedSourceAndTargetTypes() { + Order order = new Order(); + order.setItem( "my item" ); + + OrderDto dto = OrderMapper.INSTANCE.orderToDto( order ); + assertEquals( order.getItem(), dto.getItem() ); + } +} diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml new file mode 100644 index 0000000000..24d3436533 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-usestypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-usestypegeneration-generator + jar + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + + 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 new file mode 100644 index 0000000000..48b0609d9d --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.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.usestypegeneration; + +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; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; + +/** + * Generate conversion uses. + * + * @author Filip Hrisafov + * + */ +@SupportedAnnotationTypes("*") +public class UsesTypeGenerationProcessor extends AbstractProcessor { + + private boolean hasRun = false; + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if ( !hasRun ) { + try { + JavaFileObject dto = processingEnv.getFiler().createSourceFile( "org.mapstruct.itest.usestypegeneration.usage.StringUtils" ); + Writer writer = dto.openWriter(); + + writer.append( "package org.mapstruct.itest.usestypegeneration.usage;" ); + writer.append( "\n" ); + writer.append( "public class StringUtils {" ); + writer.append( "\n" ); + writer.append( " public static String upperCase(String string) {" ); + writer.append( "\n" ); + writer.append( " return string == null ? null : string.toUpperCase();" ); + writer.append( "\n" ); + writer.append( " }" ); + writer.append( "\n" ); + writer.append( "}" ); + + writer.flush(); + writer.close(); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + + hasRun = true; + } + + return false; + } +} diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..57be8919b5 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,4 @@ +# Copyright MapStruct Authors. +# +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 +org.mapstruct.itest.usestypegeneration.UsesTypeGenerationProcessor diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/pom.xml new file mode 100644 index 0000000000..7c0d555eaa --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + org.mapstruct.itest + itest-usestypegeneration-aggregator + pom + + + generator + usage + + diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml new file mode 100644 index 0000000000..d7154c6246 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-usestypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-usestypegeneration-usage + jar + + + + org.junit.jupiter + junit-jupiter + test + + + org.mapstruct.itest + itest-usestypegeneration-generator + 1.0.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -XprintProcessorInfo + -XprintRounds + + -proc:none + + + + + diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/Order.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/Order.java new file mode 100644 index 0000000000..4d5a102aed --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/Order.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.usestypegeneration.usage; + +/** + * @author Filip Hrisafov + */ +public class Order { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderDto.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderDto.java new file mode 100644 index 0000000000..69951da5b2 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderDto.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.usestypegeneration.usage; + +/** + * @author Filip Hrisafov + */ +public class OrderDto { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderMapper.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderMapper.java new file mode 100644 index 0000000000..8b33ab17d9 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/usestypegeneration/usage/OrderMapper.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.usestypegeneration.usage; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = StringUtils.class) +public interface OrderMapper { + + OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); + + OrderDto orderToDto(Order order); +} 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 new file mode 100644 index 0000000000..55ea60a0ad --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.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.usestypegeneration.usage; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for using MapStruct with another annotation processor that generates the other mappers for uses + * + * @author Filip Hrisafov + */ +class GeneratedUsesTypeTest { + + @Test + void considersPropertiesOnGeneratedSourceAndTargetTypes() { + Order order = new Order(); + order.setItem( "my item" ); + + OrderDto dto = OrderMapper.INSTANCE.orderToDto( order ); + assertThat( dto.getItem() ).isEqualTo( "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 new file mode 100755 index 0000000000..bd8896bf22 --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# 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 +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# 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 + +# 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" + + 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 + 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 +} + +# 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 +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +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:]' +} + +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 + +# 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" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +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 + +# 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 + +mkdir -p -- "${MAVEN_HOME%/*}" + +# 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 + +# 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 + +# 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 "${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 + +# 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 + 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 + 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 +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 + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# 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 + +# 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 + +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 + +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" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000000..92450f9327 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@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 ---------------------------------------------------------------------------- + +@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) +) +@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 8ac6ba1add..b444c1847a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -1,56 +1,58 @@ 4.0.0 - - org.sonatype.oss - oss-parent - 7 - - - org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT pom MapStruct Parent An annotation processor for generating type-safe bean mappers - http://mapstruct.org/ + https://mapstruct.org/ 2012 UTF-8 - 1.0.0 - 1.2 - 2.19.1 - 2.10.3 - 4.0.3.RELEASE - 0.23.1 - 7.2 - + + 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.3 + 1.6.0 + 13.0.0 + 5.14.1 + 2.2.0 + 1.24.0 1 - 3.5.2 + 3.27.7 + + + jdt_apt + + 1.8 + 3.25.5 + 2.3.2 + 2.3.0 @@ -66,7 +68,12 @@ gunnarmorling Gunnar Morling gunnar@mapstruct.org - http://www.gunnarmorling.de/ + https://www.morling.dev/ + + + filiphr + Filip Hrisafov + https://github.com/filiphr/ @@ -77,14 +84,27 @@ HEAD + + + sonatype-nexus-staging + Nexus Release Repository + https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + GitHub Issues https://github.com/mapstruct/mapstruct/issues - Jenkins - https://mapstruct.ci.cloudbees.com/ + Github Actions + https://github.com/mapstruct/mapstruct/actions @@ -94,16 +114,12 @@ - - 3.0.3 - - org.freemarker freemarker - 2.3.21 + 2.3.34 org.assertj @@ -113,17 +129,17 @@ com.google.guava guava - 19.0 + 32.0.0-jre - com.jolira - hickory - ${com.jolira.hickory.version} + org.mapstruct.tools.gem + gem-api + ${org.mapstruct.gem.version} - junit - junit - 4.12 + org.mapstruct.tools.gem + gem-processor + ${org.mapstruct.gem.version} com.puppycrawl.tools @@ -131,33 +147,69 @@ ${com.puppycrawl.tools.checkstyle.version} + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + + + org.junit + junit-bom + ${org.junit.jupiter.version} + pom + import + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + + javax.enterprise cdi-api - 1.2 + 2.0.SP1 + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.0.1 javax.inject javax.inject 1 + + jakarta.inject + jakarta.inject-api + 2.0.1 + org.jboss.arquillian arquillian-bom - 1.0.2.Final + 1.7.2.Final import pom org.jboss.arquillian.container arquillian-weld-se-embedded-1.1 - 1.0.0.CR7 + 1.0.0.Final org.jboss.weld - weld-core - 2.3.2.Final + weld-core-impl + 3.1.8.Final + test org.glassfish @@ -188,11 +240,68 @@ 1.1.3 + + org.projectlombok + lombok + 1.18.38 + + + org.immutables + value + 2.5.6 + + + com.google.auto.value + auto-value + 1.5 + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + org.inferred + freebuilder + 1.14.6 + + + + + org.jspecify + jspecify + 1.0.0 + + joda-time joda-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 @@ -204,28 +313,33 @@ org.codehaus.plexus plexus-container-default - 1.6 + 1.7.1 org.codehaus.plexus - plexus-utils - 3.0.20 + plexus-component-annotations + 1.7.1 - - - ${project.groupId} - mapstruct-common - ${project.version} + org.codehaus.plexus + plexus-classworlds + 2.5.1 - ${project.groupId} - mapstruct - ${project.version} + org.codehaus.plexus + plexus-utils + 3.0.24 + + + commons-io + commons-io + 2.15.0 + + ${project.groupId} - mapstruct-jdk8 + mapstruct ${project.version} @@ -259,12 +373,12 @@ org.apache.maven.plugins maven-assembly-plugin - 2.4 + 3.8.0 org.apache.maven.plugins maven-checkstyle-plugin - 2.17 + 3.6.0 build-config/checkstyle.xml true @@ -278,7 +392,7 @@ specified as patterns within a source folder, so we can't exclude generated-sources altogether --> - **/*Prism.java,*/itest/jaxb/xsd/* + **/*Gem.java,*/itest/jaxb/xsd/* @@ -296,27 +410,25 @@ org.apache.maven.plugins maven-clean-plugin - 2.5 + 3.5.0 org.apache.maven.plugins maven-compiler-plugin - 3.3 - - 1.6 - 1.6 - -proc:none - + 3.15.0 org.apache.maven.plugins maven-dependency-plugin - 2.8 + 3.9.0 org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.4 + + true + org.apache.maven.plugins @@ -326,14 +438,14 @@ de.andrena.tools.nopackagecycles no-package-cycles-enforcer-rule - 1.0.5 + 1.0.9 org.apache.felix maven-bundle-plugin - 3.0.1 + 5.1.1 bundle-manifest @@ -347,12 +459,12 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.1.4 org.apache.maven.plugins maven-jar-plugin - 2.4 + 3.5.0 org.apache.maven.plugins @@ -360,36 +472,24 @@ ${org.apache.maven.plugins.javadoc.version} true - org.mapstruct.ap.internal.prism;org.mapstruct.itest.jaxb.xsd.* - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.1 - - -Psonatype-oss-release -DskipTests ${add.release.arguments} - clean install - false - true - @{project.version} - true + org.mapstruct.ap.internal.gem;org.mapstruct.itest.jaxb.xsd.* + 8 org.apache.maven.plugins maven-resources-plugin - 2.6 + 3.4.0 org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.4.0 org.apache.maven.plugins maven-site-plugin - 3.3 + 3.21.0 org.apache.maven.plugins @@ -397,47 +497,41 @@ ${org.apache.maven.plugins.surefire.version} ${forkCount} + -Xms1024m -Xmx3072m - - - org.apache.maven.surefire - surefire-junit47 - ${org.apache.maven.plugins.surefire.version} - - org.apache.maven.plugins maven-shade-plugin - 2.3 + 3.6.1 - com.mycila.maven-license-plugin - maven-license-plugin - 1.10.b1 + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} - org.bsc.maven - maven-processor-plugin - 2.2.4 - - - org.codehaus.mojo - build-helper-maven-plugin - 1.8 + com.mycila + license-maven-plugin + 4.6 org.codehaus.mojo animal-sniffer-maven-plugin - 1.9 + 1.20 org.ow2.asm - asm-all - 5.0.2 + asm + 7.0 + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + org.eclipse.m2e lifecycle-mapping @@ -492,47 +586,112 @@ org.jacoco jacoco-maven-plugin - 0.7.1.201405082137 + 0.8.14 org.jvnet.jaxb2.maven2 maven-jaxb2-plugin - 0.9.0 + 0.14.0 org.codehaus.mojo properties-maven-plugin 1.0-alpha-2 + + de.thetaphi + forbiddenapis + 2.6 + + + ${project.basedir}/../etc/forbidden-apis.txt + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.25.4 + + + verify + + cmp + + + + + + + ${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging} + + + + \d+\.\d+\.\d+\.Final + true + + + METHOD_NEW_DEFAULT + true + false + MINOR + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.2.2 + + - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -

${basedir}/../etc/license.txt
true - - .idea/** - **/build-config/checkstyle.xml - **/build-config/import-control.xml - copyright.txt - **/LICENSE.txt - **/mapstruct.xml - **/toolchains-*.xml - **/eclipse-formatter-config.xml - **/checkstyle-for-generated-sources.xml - **/nb-configuration.xml - maven-settings.xml - readme.md - CONTRIBUTING.md - .gitattributes - .gitignore - .factorypath - .checkstyle - + + +
${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_STYLE + SLASHSTAR_STYLE + @@ -555,9 +714,12 @@ org.codehaus.mojo.signature - java16 + java18 1.0 + + org.mapstruct.ap.internal.util.IgnoreJRERequirement + @@ -577,7 +739,7 @@ - [1.8,) + [${minimum.java.version},) @@ -595,7 +757,9 @@ test-compile - + + false + @@ -614,18 +778,66 @@ + + org.codehaus.mojo + flatten-maven-plugin + + + + flatten + package + + flatten + + + true + ossrh + + expand + + + + + flatten-clean + clean + + clean + + + + + + org.codehaus.mojo + versions-maven-plugin + - sonatype-oss-release + publication + + + release + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + org.apache.maven.plugins maven-javadoc-plugin - ${org.apache.maven.plugins.javadoc.version} attach-javadocs @@ -638,5 +850,94 @@ + + stage + + local::file:${maven.multiModuleProjectDirectory}/target/staging-deploy + + + deploy + + + + jreleaser + + + + 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 798a9128e2..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,9 @@ @@ -26,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT parent/pom.xml @@ -37,29 +24,28 @@ parent build-config - core-common core core-jdk8 processor - integrationtest + + true + - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
etc/license.txt
+ + +
etc/license.txt
+
+
XML_STYLE + SLASHSTAR_STYLE
@@ -88,5 +74,17 @@ distribution + + test + + + release + !true + + + + integrationtest + +
diff --git a/processor/pom.xml b/processor/pom.xml index c16448f8f3..e317365aec 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.2.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -34,7 +21,10 @@ MapStruct Processor - ${project.build.directory}/generated-sources/prims + + + 21 @@ -43,30 +33,39 @@ org.freemarker freemarker - - - com.jolira - hickory - provided + org.mapstruct.tools.gem + gem-api ${project.groupId} mapstruct provided + + org.jetbrains.kotlin + kotlin-metadata-jvm + provided + + + org.jetbrains.kotlin + kotlin-compiler-embeddable + test + org.eclipse.tycho tycho-compiler-jdt - provided - true + test - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine test @@ -95,6 +94,16 @@ javax.inject test + + jakarta.inject + jakarta.inject-api + test + + + jakarta.enterprise + jakarta.enterprise.cdi-api + test + @@ -120,12 +129,38 @@ test + + org.junit.platform + junit-platform-launcher + test + + + + org.junit-pioneer + junit-pioneer + test + + joda-time joda-time test + + + + org.jspecify + jspecify + test + + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -137,12 +172,30 @@ true + + org.mapstruct.processor + annotation-processor +
+ + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin + + + + @{jacocoArgLine} -Xms1024m -Xmx3072m + org.apache.maven.plugins @@ -179,6 +232,12 @@ true + + + org.freemarker:* + org.mapstruct.tools.gem:gem-api + + org.freemarker:freemarker @@ -186,17 +245,44 @@ META-INF/*.* + + org.mapstruct.tools.gem:gem-api + + META-INF/*.* + **/GemDefinition.class + **/GemDefinitions.class + + freemarker org.mapstruct.ap.shaded.freemarker + + org.mapstruct.tools.gem + org.mapstruct.ap.shaded.org.mapstruct.tools.gem + + + + org.codehaus.mojo + flatten-maven-plugin + + + + flatten + package + + flatten + + + + org.apache.maven.plugins maven-dependency-plugin @@ -221,52 +307,82 @@ - org.bsc.maven - maven-processor-plugin + org.jetbrains.kotlin + + kotlin-maven-plugin - ${prism.directory} - - net.java.dev.hickory.prism.internal.PrismGenerator - - + false - process - generate-sources + kotlin-compile + compile - process + compile + + ${minimum.java.version} + + src/main/kotlin + + src/main/java + + + + + kotlin-test-compile + test-compile + + test-compile + + + ${minimum.java.version} + + src/test/java + + - - - com.jolira - hickory - ${com.jolira.hickory.version} - - - org.eclipse.tycho - tycho-compiler-jdt - ${org.eclipse.tycho.compiler-jdt.version} - - - org.codehaus.mojo - build-helper-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin + + ${minimum.java.version} + + + org.mapstruct.tools.gem + gem-processor + ${org.mapstruct.gem.version} + + + + + - add-source - generate-sources + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile - add-source + compile + + + + java-test-compile + test-compile + + testCompile - - - ${prism.directory} - - @@ -300,6 +416,9 @@ + + jacocoArgLine + org.apache.maven.plugins @@ -314,7 +433,49 @@ + + de.thetaphi + forbiddenapis + + + + check + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + + + + org.mapstruct.ap + + + org.mapstruct.ap.internal + org.mapstruct.ap.shaded + + + + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + true + + + + diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 9db2511efa..50831a8f42 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap; @@ -26,31 +13,41 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; 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; +import javax.lang.model.element.Name; +import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.ElementKindVisitor6; +import javax.lang.model.type.TypeMirror; +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.prism.MapperPrism; -import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; 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.TypeHierarchyErroneousException; +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; /** * A JSR 269 annotation {@link Processor} which generates the implementations for mapper interfaces (interfaces @@ -72,9 +69,9 @@ *
  • if no error occurred, write out the model into Java source files
  • * *

    - * For reading annotation attributes, prisms as generated with help of the Hickory tool are used. These prisms 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 @@ -85,12 +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 -}) public class MappingProcessor extends AbstractProcessor { /** @@ -98,15 +89,40 @@ 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 ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; + // 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; + private AnnotationProcessorContext annotationProcessorContext; + /** * Any mappers for which an implementation cannot be generated in the current round because they have source/target * types with incomplete hierarchies (as super-types are to be generated by other processors). They will be @@ -114,27 +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(); + 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(); - } - - 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 ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, - processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ) + options = new Options( processingEnv.getOptions() ); + annotationProcessorContext = new AnnotationProcessorContext( + processingEnv.getElementUtils(), + processingEnv.getTypeUtils(), + processingEnv.getMessager(), + options.isDisableBuilders(), + options.isDisableJSpecify(), + options.isVerbose(), + resolveAdditionalOptions( processingEnv.getOptions() ) ); + + if ( additionalSupportedOptionsError != null ) { + processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError ); + } } @Override @@ -146,26 +178,73 @@ public SourceVersion getSupportedSourceVersion() { public boolean process(final Set annotations, final RoundEnvironment roundEnvironment) { // nothing to do in the last round if ( !roundEnvironment.processingOver() ) { + RoundContext roundContext = new RoundContext( annotationProcessorContext ); + // process any mappers left over from previous rounds Set deferredMappers = getAndResetDeferredMappers(); - processMapperElements( deferredMappers ); + processMapperElements( deferredMappers, roundContext ); // get and process any mappers from this round Set mappers = getMappers( annotations, roundEnvironment ); - processMapperElements( mappers ); + processMapperElements( mappers, roundContext ); + } + else if ( !deferredMappers.isEmpty() ) { + // If the processing is over and there are deferred mappers it means something wrong occurred and + // MapStruct didn't generate implementations for those + for ( DeferredMapper deferredMapper : deferredMappers ) { + + TypeElement deferredMapperElement = deferredMapper.deferredMapperElement; + Element erroneousElement = deferredMapper.erroneousElement; + String erroneousElementName; + + if ( erroneousElement instanceof QualifiedNameable ) { + erroneousElementName = ( (QualifiedNameable) erroneousElement ).getQualifiedName().toString(); + } + else { + erroneousElementName = + erroneousElement != null ? erroneousElement.getSimpleName().toString() : null; + } + + // When running on Java 8 we need to fetch the deferredMapperElement again. + // Otherwise the reporting will not work properly + deferredMapperElement = annotationProcessorContext.getElementUtils() + .getTypeElement( deferredMapperElement.getQualifiedName() ); + + processingEnv.getMessager() + .printMessage( + Kind.ERROR, + "No implementation was created for " + deferredMapperElement.getSimpleName() + + " due to having a problem in the erroneous element " + erroneousElementName + "." + + " Hint: this often means that some other annotation processor was supposed to" + + " process the erroneous element. You can also enable MapStruct verbose mode by setting" + + " -Amapstruct.verbose=true as a compilation argument.", + deferredMapperElement + ); + } + } 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). */ private Set getAndResetDeferredMappers() { - Set deferred = new HashSet( deferredMappers.size() ); + Set deferred = new HashSet<>( deferredMappers.size() ); - for (TypeElement element : deferredMappers ) { + for ( DeferredMapper deferredMapper : deferredMappers ) { + TypeElement element = deferredMapper.deferredMapperElement; deferred.add( processingEnv.getElementUtils().getTypeElement( element.getQualifiedName() ) ); } @@ -175,7 +254,7 @@ private Set getAndResetDeferredMappers() { private Set getMappers(final Set annotations, final RoundEnvironment roundEnvironment) { - Set mapperTypes = new HashSet(); + Set mapperTypes = new HashSet<>(); for ( TypeElement annotation : annotations ) { //Indicates that the annotation's type isn't on the class path of the compiled @@ -186,12 +265,12 @@ private Set getMappers(final Set annotations try { Set annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation ); - for (Element mapperElement : annotatedMappers) { + for ( Element mapperElement : annotatedMappers ) { TypeElement mapperTypeElement = asTypeElement( mapperElement ); // on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with // annotations unknown to the compiler, even though they are not declared Mappers - if ( mapperTypeElement != null && MapperPrism.getInstanceOn( mapperTypeElement ) != null ) { + if ( mapperTypeElement != null && MapperGem.instanceOn( mapperTypeElement ) != null ) { mapperTypes.add( mapperTypeElement ); } } @@ -204,7 +283,7 @@ private Set getMappers(final Set annotations return mapperTypes; } - private void processMapperElements(Set mapperElements) { + private void processMapperElements(Set mapperElements, RoundContext roundContext) { for ( TypeElement mapperElement : mapperElements ) { try { // create a new context for each generated mapper in order to have imports of referenced types @@ -212,11 +291,28 @@ private void processMapperElements(Set mapperElements) { // note that this assumes that a new source file is created for each mapper which must not // necessarily be the case, e.g. in case of several mapper interfaces declared as inner types // of one outer interface - ProcessorContext context = new DefaultModelElementProcessorContext( processingEnv, options ); + List tst = mapperElement.getEnclosedElements(); + ProcessorContext context = new DefaultModelElementProcessorContext( + processingEnv, + options, + roundContext, + getDeclaredTypesNotToBeImported( mapperElement ), + mapperElement + ); + processMapperTypeElement( context, mapperElement ); } catch ( TypeHierarchyErroneousException thie ) { - deferredMappers.add( mapperElement ); + TypeMirror erroneousType = thie.getType(); + Element erroneousElement = erroneousType != null ? roundContext.getAnnotationProcessorContext() + .getTypeUtils() + .asElement( erroneousType ) : null; + if ( options.isVerbose() ) { + processingEnv.getMessager().printMessage( + Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: " + + mapperElement ); + } + deferredMappers.add( new DeferredMapper( mapperElement, erroneousElement ) ); } catch ( Throwable t ) { handleUncaughtError( mapperElement, t ); @@ -225,11 +321,19 @@ private void processMapperElements(Set mapperElements) { } } + private Map getDeclaredTypesNotToBeImported(TypeElement element) { + return element.getEnclosedElements().stream() + .filter( e -> CLASS.equals( e.getKind() ) ) + .map( Element::getSimpleName ) + .map( Name::toString ) + .collect( Collectors.toMap( k -> k, v -> element.getQualifiedName().toString() + "." + v ) ); + } + private void handleUncaughtError(Element element, Throwable thrown) { StringWriter sw = new StringWriter(); thrown.printStackTrace( new PrintWriter( sw ) ); - String reportableStacktrace = sw.toString().replace( System.getProperty( "line.separator" ), " " ); + String reportableStacktrace = sw.toString().replace( System.lineSeparator( ), " " ); processingEnv.getMessager().printMessage( Kind.ERROR, "Internal error in the mapping processor: " + reportableStacktrace, element ); @@ -272,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. @@ -286,20 +390,20 @@ private R process(ProcessorContext context, ModelElementProcessor p MappingProcessor.class.getClassLoader() ) .iterator(); - List> processors = new ArrayList>(); + List> processors = new ArrayList<>(); while ( processorIterator.hasNext() ) { 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; @@ -314,14 +418,63 @@ 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 public int compare(ModelElementProcessor o1, ModelElementProcessor o2) { - return - o1.getPriority() < o2.getPriority() ? -1 : - o1.getPriority() == o2.getPriority() ? 0 : - 1; + return Integer.compare( o1.getPriority(), o2.getPriority() ); + } + } + + private static class DeferredMapper { + + private final TypeElement deferredMapperElement; + private final Element erroneousElement; + + private DeferredMapper(TypeElement deferredMapperElement, Element erroneousElement) { + this.deferredMapperElement = deferredMapperElement; + 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 a9c7f4b012..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 @@ -1,29 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Collections; -import org.mapstruct.ap.internal.util.JavaTimeConstants; import org.mapstruct.ap.internal.util.Strings; /** @@ -32,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 *

    */ public abstract class AbstractJavaTimeToStringConversion extends SimpleConversion { @@ -51,11 +40,11 @@ protected String getToExpression(ConversionContext conversionContext) { } private String dateTimeFormatter(ConversionContext conversionContext) { - if ( !Strings.isEmpty( conversionContext.getDateFormat() ) ) { - return "DateTimeFormatter.ofPattern( \"" + conversionContext.getDateFormat() + "\" )"; + if ( Strings.isNotEmpty( conversionContext.getDateFormat() ) ) { + return GetDateTimeFormatterField.getDateTimeFormatterFieldName( conversionContext.getDateFormat() ); } else { - return "DateTimeFormatter." + defaultFormatterSuffix(); + return ConversionUtils.dateTimeFormatter( conversionContext ) + "." + defaultFormatterSuffix(); } } @@ -64,7 +53,7 @@ private String dateTimeFormatter(ConversionContext conversionContext) { @Override protected String getFromExpression(ConversionContext conversionContext) { // See http://docs.oracle.com/javase/tutorial/datetime/iso/format.html for how to parse Dates - return new StringBuilder().append( conversionContext.getTargetType().getFullyQualifiedName() ) + return new StringBuilder().append( conversionContext.getTargetType().createReferenceName() ) .append( ".parse( " ) .append( parametersListForParsing( conversionContext ) ) .append( " )" ).toString(); @@ -74,9 +63,8 @@ private String parametersListForParsing(ConversionContext conversionContext) { // See http://docs.oracle.com/javase/tutorial/datetime/iso/format.html for how to format Dates StringBuilder parameterBuilder = new StringBuilder( "" ); if ( !Strings.isEmpty( conversionContext.getDateFormat() ) ) { - parameterBuilder.append( ", DateTimeFormatter.ofPattern( \"" ) - .append( conversionContext.getDateFormat() ) - .append( "\" )" ); + parameterBuilder.append( ", " ); + parameterBuilder.append( dateTimeFormatter( conversionContext ) ); } return parameterBuilder.toString(); } @@ -84,14 +72,31 @@ private String parametersListForParsing(ConversionContext conversionContext) { @Override protected Set getToConversionImportTypes(ConversionContext conversionContext) { return Collections.asSet( - conversionContext.getTypeFactory().getType( JavaTimeConstants.DATE_TIME_FORMATTER_FQN ) + conversionContext.getTypeFactory().getType( DateTimeFormatter.class ) ); } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return Collections.asSet( - conversionContext.getTypeFactory().getType( JavaTimeConstants.DATE_TIME_FORMATTER_FQN ) - ); + if ( !Strings.isEmpty( conversionContext.getDateFormat() ) ) { + return Collections.asSet( + conversionContext.getTargetType(), + conversionContext.getTypeFactory().getType( DateTimeFormatter.class ) + ); + } + + return Collections.asSet( conversionContext.getTargetType() ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + if ( Strings.isNotEmpty( conversionContext.getDateFormat() ) ) { + return java.util.Collections.singletonList( + new GetDateTimeFormatterField( + conversionContext.getTypeFactory(), + conversionContext.getDateFormat() ) ); + } + + return super.getRequiredHelperFields( conversionContext ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJodaTypeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJodaTypeToStringConversion.java index 54afca8a41..c0a3d56481 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJodaTypeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJodaTypeToStringConversion.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.util.Collections; import java.util.Locale; import java.util.Set; @@ -28,6 +13,10 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.JodaTimeConstants; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.dateTimeFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Base class for conversions between Joda-Time types and String. * @@ -78,7 +67,7 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon } private String conversionString(ConversionContext conversionContext, String method) { - StringBuilder conversionString = new StringBuilder( "DateTimeFormat" ); + StringBuilder conversionString = new StringBuilder( dateTimeFormat( conversionContext ) ); conversionString.append( dateFormatPattern( conversionContext ) ); conversionString.append( "." ); conversionString.append( method ); @@ -92,7 +81,7 @@ private String dateFormatPattern(ConversionContext conversionContext) { String dateFormat = conversionContext.getDateFormat(); if ( dateFormat == null ) { - conversionString.append( defaultDateFormatPattern() ); + conversionString.append( defaultDateFormatPattern( conversionContext ) ); } else { @@ -105,8 +94,12 @@ private String dateFormatPattern(ConversionContext conversionContext) { return conversionString.toString(); } - private String defaultDateFormatPattern() { - return " DateTimeFormat.patternForStyle( \"" + formatStyle() + "\", Locale.getDefault() )"; + private String defaultDateFormatPattern(ConversionContext conversionContext) { + return " " + + dateTimeFormat( conversionContext ) + + ".patternForStyle( \"" + formatStyle() + "\", " + + locale( conversionContext ) + + ".getDefault() )"; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractNumberToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractNumberToStringConversion.java index bac6bf1f36..99649a9a37 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractNumberToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractNumberToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; @@ -40,7 +27,6 @@ public abstract class AbstractNumberToStringConversion extends SimpleConversion private final boolean sourceTypeNumberSubclass; public AbstractNumberToStringConversion(boolean sourceTypeNumberSubclass) { - this.sourceTypeNumberSubclass = sourceTypeNumberSubclass; } @@ -77,5 +63,4 @@ protected List getFromConversionExceptionTypes(ConversionContext conversio return super.getFromConversionExceptionTypes( conversionContext ); } } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToBigIntegerConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToBigIntegerConversion.java index dbf4e19002..6268371480 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToBigIntegerConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToBigIntegerConversion.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigDecimal; import java.math.BigInteger; import java.util.Set; @@ -27,6 +12,9 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigDecimal} and {@link BigInteger}. * @@ -41,11 +29,14 @@ public String getToExpression(ConversionContext conversionContext) { @Override public String getFromExpression(ConversionContext conversionContext) { - return "new BigDecimal( )"; + return "new " + + bigDecimal( conversionContext ) + + "( )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToPrimitiveConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToPrimitiveConversion.java index 8f15874103..ff50b910cd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToPrimitiveConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToPrimitiveConversion.java @@ -1,31 +1,19 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigDecimal; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigDecimal} and native number types. * @@ -36,10 +24,6 @@ public class BigDecimalToPrimitiveConversion extends SimpleConversion { private final Class targetType; public BigDecimalToPrimitiveConversion(Class targetType) { - if ( !targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is no primitive type." ); - } - this.targetType = targetType; } @@ -50,11 +34,12 @@ public String getToExpression(ConversionContext conversionContext) { @Override public String getFromExpression(ConversionContext conversionContext) { - return "BigDecimal.valueOf( )"; + return bigDecimal( conversionContext ) + ".valueOf( )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java index e03e215d0d..384013a7b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java @@ -1,27 +1,12 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigDecimal; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -29,6 +14,10 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigDecimal} and {@link String}. * @@ -57,13 +46,13 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "(BigDecimal) " ); + sb.append( "(" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); return sb.toString(); } else { - return "new BigDecimal( )"; + return "new " + bigDecimal( conversionContext ) + "( )"; } } @@ -74,19 +63,34 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon @Override public List getRequiredHelperMethods(ConversionContext conversionContext) { - HelperMethod helperMethod = new CreateDecimalFormat( conversionContext.getTypeFactory() ); - return Arrays.asList( helperMethod ); + List helpers = new ArrayList<>(); + if ( conversionContext.getNumberFormat() != null ) { + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); + } + return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToWrapperConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToWrapperConversion.java index 454e64bec9..ef5a3175e7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToWrapperConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToWrapperConversion.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigDecimal; import java.util.Set; @@ -27,6 +12,9 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.NativeTypes; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigDecimal} and wrappers of native number types. * @@ -37,10 +25,6 @@ public class BigDecimalToWrapperConversion extends SimpleConversion { private final Class targetType; public BigDecimalToWrapperConversion(Class targetType) { - if ( targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is a primitive type." ); - } - this.targetType = NativeTypes.getPrimitiveType( targetType ); } @@ -51,11 +35,12 @@ public String getToExpression(ConversionContext conversionContext) { @Override public String getFromExpression(ConversionContext conversionContext) { - return "BigDecimal.valueOf( )"; + return bigDecimal( conversionContext ) + ".valueOf( )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToPrimitiveConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToPrimitiveConversion.java index 7af28b0587..75c3647bad 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToPrimitiveConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToPrimitiveConversion.java @@ -1,31 +1,19 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigInteger; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigInteger} and native number types. * @@ -36,10 +24,6 @@ public class BigIntegerToPrimitiveConversion extends SimpleConversion { private final Class targetType; public BigIntegerToPrimitiveConversion(Class targetType) { - if ( !targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is no primitive type." ); - } - this.targetType = targetType; } @@ -54,11 +38,12 @@ public String getFromExpression(ConversionContext conversionContext) { if ( targetType == float.class || targetType == double.class ) { castString = "(long) "; } - return "BigInteger.valueOf( " + castString + " )"; + return bigInteger( conversionContext ) + ".valueOf( " + castString + " )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigInteger.class ) ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java index b5f5a34219..dc3bd29cd8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - +import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -29,6 +15,11 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigInteger} and {@link String}. * @@ -57,22 +48,21 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "( (BigDecimal) " ); + sb.append( "( (" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); sb.append( " ).toBigInteger()" ); return sb.toString(); } else { - return "new BigInteger( )"; + return "new " + bigInteger( conversionContext ) + "( )"; } } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - // no imports are required when decimal format is used. - return super.getFromConversionImportTypes( conversionContext ); + return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); } else { return asSet( conversionContext.getTypeFactory().getType( BigInteger.class ) ); @@ -81,17 +71,33 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon @Override public List getRequiredHelperMethods(ConversionContext conversionContext) { - HelperMethod helperMethod = new CreateDecimalFormat( conversionContext.getTypeFactory() ); - return Arrays.asList( helperMethod ); + List helpers = new ArrayList<>(); + if ( conversionContext.getNumberFormat() != null ) { + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); + } + return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToWrapperConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToWrapperConversion.java index 017c987091..b3aa5dc6f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToWrapperConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToWrapperConversion.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.math.BigInteger; import java.util.Set; @@ -27,6 +12,9 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.NativeTypes; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link BigInteger} and wrappers of native number types. * @@ -37,10 +25,6 @@ public class BigIntegerToWrapperConversion extends SimpleConversion { private final Class targetType; public BigIntegerToWrapperConversion(Class targetType) { - if ( targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is a primitive type." ); - } - this.targetType = NativeTypes.getPrimitiveType( targetType ); } @@ -56,11 +40,12 @@ public String getFromExpression(ConversionContext conversionContext) { toLongValueStr = ".longValue()"; } - return "BigInteger.valueOf( " + toLongValueStr + " )"; + return bigInteger( conversionContext ) + ".valueOf( " + toLongValueStr + " )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigInteger.class ) ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharToStringConversion.java index e844972b2a..fa6434b582 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharWrapperToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharWrapperToStringConversion.java index f4d731a560..5dd6d6bf73 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharWrapperToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CharWrapperToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java index b07a747d60..b392281bf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java @@ -1,28 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.util.Collections; import java.util.List; + +import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.TypeConversion; -import org.mapstruct.ap.internal.model.assignment.Assignment; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; -import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.common.FieldReference; /** * Implementations create inline {@link TypeConversion}s such as @@ -56,9 +46,23 @@ public interface ConversionProvider { Assignment from(ConversionContext conversionContext); /** + * Retrieves any helper methods required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any helper methods when required. */ List getRequiredHelperMethods(ConversionContext conversionContext); + + /** + * Retrieves any fields required for creating the conversion. + * + * @param conversionContext ConversionContext providing optional information required for creating the conversion. + * + * @return any fields when required. + */ + default List getRequiredHelperFields(ConversionContext conversionContext) { + return Collections.emptyList(); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java new file mode 100644 index 0000000000..e014e30f27 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -0,0 +1,294 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Currency; +import java.util.Locale; +import java.util.UUID; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.util.JodaTimeConstants; + +/** + * Methods mainly used in {@link org.mapstruct.ap.internal.conversion.SimpleConversion SimpleConversion} classes, e. g. + * to get the correct String representation of various types that could be used in generated sources. + * + * @author Christian Bandowski + */ +public final class ConversionUtils { + private ConversionUtils() { + } + + /** + * Name for the given {@code canonicalName}. + * + * @param conversionContext Conversion context + * @param canonicalName Canonical name of the type + * + * @return Name or fully-qualified name. + */ + private static String typeReferenceName(ConversionContext conversionContext, String canonicalName) { + return conversionContext.getTypeFactory().getType( canonicalName ).createReferenceName(); + } + + /** + * Name for the given {@code canonicalName}. + * + * @param conversionContext Conversion context + * @param type Type + * + * @return Name or fully-qualified name. + */ + private static String typeReferenceName(ConversionContext conversionContext, Class type) { + return conversionContext.getTypeFactory().getType( type ).createReferenceName(); + } + + /** + * Name for {@link java.math.BigDecimal}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String bigDecimal(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, BigDecimal.class ); + } + + /** + * Name for {@link java.math.BigInteger}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String bigInteger(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, BigInteger.class ); + } + + /** + * Name for {@link java.util.Locale}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String locale(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, Locale.class ); + } + + /** + * Name for {@link java.util.Currency}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String currency(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, Currency.class ); + } + + /** + * Name for {@link java.sql.Date}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String sqlDate(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, java.sql.Date.class ); + } + + /** + * Name for {@link java.sql.Time}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String time(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, Time.class ); + } + + /** + * Name for {@link java.sql.Timestamp}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String timestamp(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, Timestamp.class ); + } + + /** + * Name for {@link java.text.DecimalFormat}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String decimalFormat(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, DecimalFormat.class ); + } + + /** + * Name for {@link java.text.SimpleDateFormat}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String simpleDateFormat(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, SimpleDateFormat.class ); + } + + /** + * Name for {@link java.util.Date}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String date(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, java.util.Date.class ); + } + + /** + * Name for {@link java.time.ZoneOffset}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String zoneOffset(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, ZoneOffset.class ); + } + + /** + * Name for {@link java.time.ZoneId}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String zoneId(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, ZoneId.class ); + } + + /** + * Name for {@link java.time.LocalDate}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String localDate(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, LocalDate.class ); + } + + /** + * Name for {@link java.time.LocalDateTime}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String localDateTime(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, LocalDateTime.class ); + } + + /** + * Name for {@link java.time.ZonedDateTime}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String zonedDateTime(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, ZonedDateTime.class ); + } + + /** + * Name for {@link java.time.format.DateTimeFormatter}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String dateTimeFormatter(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, DateTimeFormatter.class ); + } + + /** + * Name for {@code org.joda.time.format.DateTimeFormat}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String dateTimeFormat(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, JodaTimeConstants.DATE_TIME_FORMAT_FQN ); + } + + /** + * Name for {@link java.lang.StringBuilder}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String stringBuilder(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, StringBuilder.class ); + } + + /** + * Name for {@link java.util.UUID}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String uuid(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, UUID.class ); + } + + /** + * Name for {@link java.net.URI}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String uri(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, URI.class ); + } + + /** + * Name for {@link java.text.DecimalFormatSymbols}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String decimalFormatSymbols(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, DecimalFormatSymbols.class ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 745d9bef4c..6e1f2c03aa 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -1,41 +1,39 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.conversion.ReverseConversion.reverse; - import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; +import java.net.URL; import java.sql.Time; import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.Calendar; +import java.util.Currency; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; - -import javax.lang.model.util.Elements; +import java.util.Objects; +import java.util.UUID; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.util.JavaTimeConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; + /** * Holds built-in {@link ConversionProvider}s such as from {@code int} to {@code String}. * @@ -43,16 +41,18 @@ */ public class Conversions { - private final Map conversions = new HashMap(); + private final Map conversions = new HashMap<>(); private final Type enumType; private final Type stringType; + private final Type integerType; private final TypeFactory typeFactory; - public Conversions(Elements elementUtils, TypeFactory typeFactory) { + public Conversions(TypeFactory typeFactory) { this.typeFactory = typeFactory; this.enumType = typeFactory.getType( Enum.class ); this.stringType = typeFactory.getType( String.class ); + this.integerType = typeFactory.getType( Integer.class ); //native types <> native types, including wrappers registerNativeTypeConversion( byte.class, Byte.class ); @@ -180,8 +180,9 @@ public Conversions(Elements elementUtils, TypeFactory typeFactory) { registerToStringConversion( Boolean.class ); register( char.class, String.class, new CharToStringConversion() ); register( Character.class, String.class, new CharWrapperToStringConversion() ); - register( BigInteger.class, String.class, new BigIntegerToStringConversion( ) ); - register( BigDecimal.class, String.class, new BigDecimalToStringConversion( ) ); + register( BigInteger.class, String.class, new BigIntegerToStringConversion() ); + register( BigDecimal.class, String.class, new BigDecimalToStringConversion() ); + register( StringBuilder.class, String.class, new StringBuilderToStringConversion() ); registerJodaConversions(); @@ -189,11 +190,20 @@ public Conversions(Elements elementUtils, TypeFactory typeFactory) { //misc. register( Enum.class, String.class, new EnumStringConversion() ); + register( Enum.class, Integer.class, new EnumToIntegerConversion() ); + register( Enum.class, int.class, new EnumToIntegerConversion() ); register( Date.class, String.class, new DateToStringConversion() ); register( BigDecimal.class, BigInteger.class, new BigDecimalToBigIntegerConversion() ); - register( Date.class, Time.class, new DateToSqlTimeConversion() ); - register( Date.class, java.sql.Date.class, new DateToSqlDateConversion() ); - register( Date.class, Timestamp.class, new DateToSqlTimestampConversion() ); + + registerJavaTimeSqlConversions(); + + // java.util.Currency <~> String + register( Currency.class, String.class, new CurrencyToStringConversion() ); + + register( UUID.class, String.class, new UUIDToStringConversion() ); + register( Locale.class, String.class, new LocaleToStringConversion() ); + + registerJavaNetConversions(); } private void registerJodaConversions() { @@ -217,40 +227,58 @@ private void registerJodaConversions() { } private void registerJava8TimeConversions() { - if ( !isJava8TimeAvailable() ) { - return; - } // Java 8 time to String - register( JavaTimeConstants.ZONED_DATE_TIME_FQN, String.class, new JavaZonedDateTimeToStringConversion() ); - register( JavaTimeConstants.LOCAL_DATE_FQN, String.class, new JavaLocalDateToStringConversion() ); - register( JavaTimeConstants.LOCAL_DATE_TIME_FQN, String.class, new JavaLocalDateTimeToStringConversion() ); - register( JavaTimeConstants.LOCAL_TIME_FQN, String.class, new JavaLocalTimeToStringConversion() ); + register( ZonedDateTime.class, String.class, new JavaZonedDateTimeToStringConversion() ); + register( LocalDate.class, String.class, new JavaLocalDateToStringConversion() ); + register( LocalDateTime.class, String.class, new JavaLocalDateTimeToStringConversion() ); + register( LocalTime.class, String.class, new JavaLocalTimeToStringConversion() ); + register( Instant.class, String.class, new StaticParseToStringConversion() ); + register( Period.class, String.class, new StaticParseToStringConversion() ); + register( Duration.class, String.class, new StaticParseToStringConversion() ); + + // Java 8 time to Date + register( ZonedDateTime.class, Date.class, new JavaZonedDateTimeToDateConversion() ); + register( LocalDateTime.class, Date.class, new JavaLocalDateTimeToDateConversion() ); + register( LocalDate.class, Date.class, new JavaLocalDateToDateConversion() ); + register( Instant.class, Date.class, new JavaInstantToDateConversion() ); + + // Java 8 time + register( LocalDateTime.class, LocalDate.class, new JavaLocalDateTimeToLocalDateConversion() ); + register( ZonedDateTime.class, LocalDateTime.class, new JavaZonedDateTimeToLocalDateTimeConversion() ); + register( OffsetDateTime.class, LocalDateTime.class, new JavaOffsetDateTimeToLocalDateTimeConversion() ); + + register( ZonedDateTime.class, Instant.class, new JavaZonedDateTimeToInstantConversion() ); + register( OffsetDateTime.class, Instant.class, new JavaOffsetDateTimeToInstantConversion() ); + } - // Java 8 to Date - register( JavaTimeConstants.ZONED_DATE_TIME_FQN, Date.class, new JavaZonedDateTimeToDateConversion() ); - register( JavaTimeConstants.LOCAL_DATE_TIME_FQN, Date.class, new JavaLocalDateTimeToDateConversion() ); - register( JavaTimeConstants.LOCAL_DATE_FQN, Date.class, new JavaLocalDateToDateConversion() ); + private void registerJavaTimeSqlConversions() { + if ( isJavaSqlAvailable() ) { + register( LocalDate.class, java.sql.Date.class, new JavaLocalDateToSqlDateConversion() ); + register( Date.class, Time.class, new DateToSqlTimeConversion() ); + register( Date.class, java.sql.Date.class, new DateToSqlDateConversion() ); + register( Date.class, Timestamp.class, new DateToSqlTimestampConversion() ); + } } private boolean isJodaTimeAvailable() { return typeFactory.isTypeAvailable( JodaTimeConstants.DATE_TIME_FQN ); } - private boolean isJava8TimeAvailable() { - return typeFactory.isTypeAvailable( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + private boolean isJavaSqlAvailable() { + return typeFactory.isTypeAvailable( "java.sql.Date" ); } private void registerNativeTypeConversion(Class sourceType, Class targetType) { if ( sourceType.isPrimitive() && targetType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToPrimitiveConversion( sourceType ) ); } - else if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) { + else if ( sourceType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToWrapperConversion( sourceType, targetType ) ); } - else if ( !sourceType.isPrimitive() && targetType.isPrimitive() ) { - register( sourceType, targetType, reverse( new PrimitiveToWrapperConversion( targetType, sourceType ) ) ); + else if ( targetType.isPrimitive() ) { + register( sourceType, targetType, inverse( new PrimitiveToWrapperConversion( targetType, sourceType ) ) ); } else { register( sourceType, targetType, new WrapperToWrapperConversion( sourceType, targetType ) ); @@ -284,12 +312,23 @@ private void registerBigDecimalConversion(Class targetType) { } } + private void registerJavaNetConversions() { + if ( isJavaNetAvailable() ) { + register( URI.class, String.class, new URIToStringConversion() ); + register( URL.class, String.class, new URLToStringConversion() ); + } + } + + private boolean isJavaNetAvailable() { + return typeFactory.isTypeAvailable( "java.net.URL" ); + } + private void register(Class sourceClass, Class targetClass, ConversionProvider conversion) { Type sourceType = typeFactory.getType( sourceClass ); Type targetType = typeFactory.getType( targetClass ); conversions.put( new Key( sourceType, targetType ), conversion ); - conversions.put( new Key( targetType, sourceType ), reverse( conversion ) ); + conversions.put( new Key( targetType, sourceType ), inverse( conversion ) ); } private void register(String sourceTypeName, Class targetClass, ConversionProvider conversion) { @@ -297,14 +336,55 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro Type targetType = typeFactory.getType( targetClass ); conversions.put( new Key( sourceType, targetType ), conversion ); - conversions.put( new Key( targetType, sourceType ), reverse( conversion ) ); + conversions.put( new Key( targetType, sourceType ), inverse( conversion ) ); } public ConversionProvider getConversion(Type sourceType, Type targetType) { - if ( sourceType.isEnumType() && targetType.equals( stringType ) ) { + if ( sourceType.isOptionalType() ) { + if ( targetType.isOptionalType() ) { + // We cannot convert optional to optional + return null; + } + Type sourceBaseType = sourceType.getOptionalBaseType(); + if ( sourceBaseType.equals( targetType ) ) { + // Optional -> Type + return TypeToOptionalConversion.OPTIONAL_TO_TYPE_CONVERSION; + } + + ConversionProvider conversionProvider = getInternalConversion( sourceBaseType, targetType ); + if ( conversionProvider != null ) { + return inverse( new OptionalWrapperConversionProvider( conversionProvider ) ); + } + + } + else if ( targetType.isOptionalType() ) { + // Type -> Optional + Type targetBaseType = targetType.getOptionalBaseType(); + if ( targetBaseType.equals( sourceType ) ) { + return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; + } + ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); + if ( conversionProvider != null ) { + return new OptionalWrapperConversionProvider( conversionProvider ); + } + return null; + + } + + return getInternalConversion( sourceType, targetType ); + } + + private ConversionProvider getInternalConversion(Type sourceType, Type targetType) { + if ( sourceType.isEnumType() && + ( targetType.equals( stringType ) || + targetType.getBoxedEquivalent().equals( integerType ) ) + ) { sourceType = enumType; } - else if ( targetType.isEnumType() && sourceType.equals( stringType ) ) { + else if ( targetType.isEnumType() && + ( sourceType.equals( stringType ) || + sourceType.getBoxedEquivalent().equals( integerType ) ) + ) { targetType = enumType; } @@ -347,23 +427,12 @@ public boolean equals(Object obj) { return false; } Key other = (Key) obj; - if ( sourceType == null ) { - if ( other.sourceType != null ) { - return false; - } - } - else if ( !sourceType.equals( other.sourceType ) ) { - return false; - } - if ( targetType == null ) { - if ( other.targetType != null ) { - return false; - } - } - else if ( !targetType.equals( other.targetType ) ) { + + if ( !Objects.equals( sourceType, other.sourceType ) ) { return false; } - return true; + + return Objects.equals( targetType, other.targetType ); } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java index 893cb385b4..d1b49cff69 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -1,32 +1,24 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.MappingMethodOptions; + +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * HelperMethod that creates a {@link java.text.DecimalFormat} @@ -39,13 +31,30 @@ public class CreateDecimalFormat extends HelperMethod { private final Parameter parameter; + private final Parameter localeParameter; private final Type returnType; private final Set importTypes; - public CreateDecimalFormat(TypeFactory typeFactory) { + public CreateDecimalFormat(TypeFactory typeFactory, boolean withLocale) { this.parameter = new Parameter( "numberFormat", typeFactory.getType( String.class ) ); + this.localeParameter = withLocale ? new Parameter( "locale", typeFactory.getType( Locale.class ) ) : null; this.returnType = typeFactory.getType( DecimalFormat.class ); - this.importTypes = asSet( parameter.getType(), returnType ); + if ( withLocale ) { + this.importTypes = asSet( + parameter.getType(), + returnType, + typeFactory.getType( DecimalFormatSymbols.class ), + typeFactory.getType( Locale.class ) + ); + } + else { + this.importTypes = asSet( parameter.getType(), returnType ); + } + } + + @Override + public String getName() { + return localeParameter == null ? "createDecimalFormat" : "createDecimalFormatWithLocale"; } @Override @@ -63,4 +72,21 @@ public Type getReturnType() { return returnType; } + @Override + public MappingMethodOptions getOptions() { + return MappingMethodOptions.empty(); + } + + @Override + public String describe() { + return null; + } + + @Override + public List getParameters() { + if ( localeParameter == null ) { + return super.getParameters(); + } + return Arrays.asList( getParameter(), localeParameter ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java new file mode 100644 index 0000000000..3f32994734 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.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.ap.internal.conversion; + +import java.util.Currency; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.currency; + +/** + * Conversion between {@link Currency} and {@link String}. + * + * @author Darren Rambaud + */ +public class CurrencyToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(final ConversionContext conversionContext) { + return ".getCurrencyCode()"; + } + + @Override + protected String getFromExpression(final ConversionContext conversionContext) { + return currency( conversionContext ) + ".getInstance( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Currency.class ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlDateConversion.java index d996a99784..bc2aae7305 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlDateConversion.java @@ -1,24 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.sql.Date; +import java.util.Collections; +import java.util.Set; + import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.sqlDate; /** * Conversion between {@link java.util.Date} and {@link java.sql.Date}. @@ -29,7 +23,12 @@ public class DateToSqlDateConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "new java.sql.Date( .getTime() )"; + return "new " + sqlDate( conversionContext ) + "( .getTime() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTypeFactory().getType( Date.class ) ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimeConversion.java index f4470ecd09..ef892273e3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimeConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; @@ -25,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Collections; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.time; + /** * Conversion between {@link java.util.Date} and {@link java.sql.Time}. * @@ -34,7 +23,7 @@ public class DateToSqlTimeConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "new Time( .getTime() )"; + return "new " + time( conversionContext ) + "( .getTime() )"; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimestampConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimestampConversion.java index a84945d93b..cba40e7c5e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimestampConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimestampConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; @@ -25,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Collections; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.timestamp; + /** * Conversion between {@link java.util.Date} and {@link java.sql.Timestamp}. * @@ -34,7 +23,7 @@ public class DateToSqlTimestampConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "new Timestamp( .getTime() )"; + return "new " + timestamp( conversionContext ) + "( .getTime() )"; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java index e43f9be4bf..35c7f88d74 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java @@ -1,38 +1,28 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static java.util.Arrays.asList; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Set; -import org.mapstruct.ap.internal.model.assignment.Assignment; -import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.TypeConversion; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.simpleDateFormat; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link String} and {@link Date}. * @@ -42,16 +32,16 @@ public class DateToStringConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - Collections.emptyList(), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.emptyList(), getConversionExpression( conversionContext, "format" ) ); } @Override public Assignment from(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - asList( conversionContext.getTypeFactory().getType( ParseException.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.singletonList( conversionContext.getTypeFactory().getType( ParseException.class ) ), getConversionExpression( conversionContext, "parse" ) ); } @@ -61,13 +51,35 @@ public List getRequiredHelperMethods(ConversionContext conversionC return Collections.emptyList(); } + private Set getImportTypes(ConversionContext conversionContext) { + if ( conversionContext.getLocale() == null ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ); + } + + return asSet( + conversionContext.getTypeFactory().getType( SimpleDateFormat.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + private String getConversionExpression(ConversionContext conversionContext, String method) { - StringBuilder conversionString = new StringBuilder( "new SimpleDateFormat(" ); + StringBuilder conversionString = new StringBuilder( "new " ); + conversionString.append( simpleDateFormat( conversionContext ) ); + conversionString.append( '(' ); if ( conversionContext.getDateFormat() != null ) { conversionString.append( " \"" ); conversionString.append( conversionContext.getDateFormat() ); - conversionString.append( "\" " ); + conversionString.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + conversionString.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + conversionString.append( conversionContext.getLocale() ); + conversionString.append( "\" ) " ); + } + else { + conversionString.append( " " ); + } } conversionString.append( ")." ); @@ -76,4 +88,5 @@ private String getConversionExpression(ConversionContext conversionContext, Stri return conversionString.toString(); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumStringConversion.java index f6856689f9..d06cad93b4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumStringConversion.java @@ -1,30 +1,17 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@link String} and {@link Enum} types. * @@ -39,11 +26,14 @@ public String getToExpression(ConversionContext conversionContext) { @Override public String getFromExpression(ConversionContext conversionContext) { - return "Enum.valueOf( " + conversionContext.getTargetType().getName() + ".class, )"; + return "Enum.valueOf( " + conversionContext.getTargetType().createReferenceName() + + ".class, )"; } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return asSet( conversionContext.getTargetType() ); + return asSet( + conversionContext.getTargetType() + ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java new file mode 100644 index 0000000000..6904d8ff63 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java @@ -0,0 +1,38 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link Enum} and {@link Integer} types. + * + * @author Jose Carlos Campanero Ortiz + */ +public class EnumToIntegerConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".ordinal()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".values()[ ]"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return asSet( + conversionContext.getTargetType() + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.java new file mode 100644 index 0000000000..052f2e7224 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.java @@ -0,0 +1,55 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.FinalField; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +public class GetDateTimeFormatterField extends FinalField implements FieldReference { + + private final String dateFormat; + + public GetDateTimeFormatterField(TypeFactory typeFactory, String dateFormat) { + super( typeFactory.getType( DateTimeFormatter.class ), getDateTimeFormatterFieldName( dateFormat ) ); + this.dateFormat = dateFormat; + } + + @Override + public Map getTemplateParameter() { + Map parameter = new HashMap<>(); + parameter.put( "dateFormat", dateFormat ); + return parameter; + } + + public static String getDateTimeFormatterFieldName(String dateFormat) { + StringBuilder sb = new StringBuilder(); + sb.append( "dateTimeFormatter_" ); + + dateFormat.codePoints().forEach( cp -> { + if ( Character.isJavaIdentifierPart( cp ) ) { + // safe to character to method name as is + sb.append( Character.toChars( cp ) ); + } + else { + // could not be used in method name + sb.append( "_" ); + } + } ); + + sb.append( "_" ); + + int hashCode = dateFormat.hashCode(); + sb.append( hashCode < 0 ? "0" : "1" ); + sb.append( Math.abs( hashCode ) ); + + return sb.toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaInstantToDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaInstantToDateConversion.java new file mode 100644 index 0000000000..3e4fe65eb9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaInstantToDateConversion.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.ap.internal.conversion; + +import java.util.Date; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.date; + +/** + * SimpleConversion for mapping {@link java.time.Instant} to + * {@link Date} and vice versa. + */ +public class JavaInstantToDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return date( conversionContext ) + ".from( )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Date.class ) ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".toInstant()"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToDateConversion.java index ff17a650d3..489fc39eb4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToDateConversion.java @@ -1,24 +1,24 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.Set; + import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.date; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.localDateTime; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneId; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; /** * SimpleConversion for mapping {@link java.time.LocalDateTime} to @@ -28,11 +28,33 @@ public class JavaLocalDateTimeToDateConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "java.util.Date.from( .toInstant( java.time.ZoneOffset.UTC ) )"; + return date( conversionContext ) + + ".from( .toInstant( " + + zoneOffset( conversionContext ) + + ".UTC ) )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( Date.class ), + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); } @Override protected String getFromExpression(ConversionContext conversionContext) { - return "java.time.LocalDateTime.ofInstant( .toInstant(), java.time.ZoneId.of( \"UTC\" ) )"; + return localDateTime( conversionContext ) + + ".ofInstant( .toInstant(), " + + zoneId( conversionContext ) + + ".of( \"UTC\" ) )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( LocalDateTime.class ), + conversionContext.getTypeFactory().getType( ZoneId.class ) + ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java new file mode 100644 index 0000000000..9116bada4c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.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.ap.internal.conversion; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.mapstruct.ap.internal.model.common.ConversionContext; + +/** + * SimpleConversion for mapping {@link LocalDateTime} to + * {@link LocalDate} and vice versa. + */ + +public class JavaLocalDateTimeToLocalDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLocalDate()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atStartOfDay()"; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToStringConversion.java index 27504ab8c4..7de296ac6a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToDateConversion.java index 1a03f3f529..b28bc50785 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToDateConversion.java @@ -1,24 +1,22 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.Set; + import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.date; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.localDateTime; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; /** * SimpleConversion for mapping {@link java.time.LocalDate} to @@ -28,12 +26,33 @@ public class JavaLocalDateToDateConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "java.util.Date.from( .atStartOfDay( java.time.ZoneOffset.UTC ).toInstant() )"; + return date( conversionContext ) + + ".from( .atStartOfDay( " + + zoneOffset( conversionContext ) + + ".UTC ).toInstant() )"; + } + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( Date.class ), + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); } @Override protected String getFromExpression(ConversionContext conversionContext) { - return "java.time.LocalDateTime.ofInstant( .toInstant(), java.time.ZoneOffset.UTC ).toLocalDate()"; + return localDateTime( conversionContext ) + + ".ofInstant( .toInstant(), " + + zoneOffset( conversionContext ) + + ".UTC ).toLocalDate()"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( LocalDateTime.class ), + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToSqlDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToSqlDateConversion.java new file mode 100644 index 0000000000..cfc6ca31d3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToSqlDateConversion.java @@ -0,0 +1,53 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.sql.Date; +import java.time.ZoneOffset; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.sqlDate; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; + +/** + * SimpleConversion for mapping {@link java.time.LocalDate} to + * {@link Date} and vice versa. + */ +public class JavaLocalDateToSqlDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return "new " + sqlDate( conversionContext ) + "( " + + ".atStartOfDay( " + + zoneOffset( conversionContext ) + + ".UTC ).toInstant().toEpochMilli() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( Date.class ), + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".toLocalDate()"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToStringConversion.java index 874e8452ab..2cc415337b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalTimeToStringConversion.java index 59a4ee0c3d..f7ca411455 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToInstantConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToInstantConversion.java new file mode 100644 index 0000000000..9c5cb63990 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToInstantConversion.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.time.ZoneOffset; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; + +public class JavaOffsetDateTimeToInstantConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toInstant()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atOffset( " + zoneOffset( conversionContext ) + ".UTC )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToLocalDateTimeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToLocalDateTimeConversion.java new file mode 100644 index 0000000000..987c4cc625 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaOffsetDateTimeToLocalDateTimeConversion.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.ap.internal.conversion; + +import java.time.ZoneOffset; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; + +public class JavaOffsetDateTimeToLocalDateTimeConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLocalDateTime()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atOffset( " + zoneOffset( conversionContext ) + ".UTC )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToDateConversion.java index 66d38cfc75..cc88d72e4e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToDateConversion.java @@ -1,24 +1,22 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.Set; + import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.date; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneId; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zonedDateTime; /** * SimpleConversion for mapping {@link java.time.ZonedDateTime} to @@ -29,11 +27,29 @@ public class JavaZonedDateTimeToDateConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return "java.util.Date.from( .toInstant() )"; + return date( conversionContext ) + ".from( .toInstant() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( Date.class ) + ); } @Override protected String getFromExpression(ConversionContext conversionContext) { - return "java.time.ZonedDateTime.ofInstant( .toInstant(), java.time.ZoneId.systemDefault() )"; + return zonedDateTime( conversionContext ) + + ".ofInstant( .toInstant(), " + + zoneId( conversionContext ) + + ".systemDefault() )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZonedDateTime.class ), + conversionContext.getTypeFactory().getType( ZoneId.class ) + ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToInstantConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToInstantConversion.java new file mode 100644 index 0000000000..44cc68c2a7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToInstantConversion.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.time.ZoneOffset; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; + +public class JavaZonedDateTimeToInstantConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toInstant()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atZone( " + zoneOffset( conversionContext ) + ".UTC )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToLocalDateTimeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToLocalDateTimeConversion.java new file mode 100644 index 0000000000..9a9275cede --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToLocalDateTimeConversion.java @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.time.ZoneOffset; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.zoneOffset; + +public class JavaZonedDateTimeToLocalDateTimeConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLocalDateTime()"; + + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atZone( " + zoneOffset( conversionContext ) + ".UTC )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( ZoneOffset.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToStringConversion.java index b43889051c..96fd4115bb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaZonedDateTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToCalendarConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToCalendarConversion.java index af9bc6fffe..d5e74e920a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToCalendarConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToCalendarConversion.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.util.Calendar; import java.util.Locale; import java.util.Set; @@ -27,6 +12,9 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between {@code DateTime} and {@link Calendar}. * @@ -36,7 +24,7 @@ public class JodaDateTimeToCalendarConversion extends SimpleConversion { @Override protected String getToExpression(ConversionContext conversionContext) { - return ".toCalendar( Locale.getDefault() )"; + return ".toCalendar( " + locale( conversionContext ) + ".getDefault() )"; } @Override @@ -46,7 +34,7 @@ protected Set getToConversionImportTypes(ConversionContext conversionConte @Override protected String getFromExpression(ConversionContext conversionContext) { - return "new " + conversionContext.getTargetType().getName() + "( )"; + return "new " + conversionContext.getTargetType().createReferenceName() + "( )"; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToStringConversion.java index 57b0ca4dbe..6980bf79fc 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaDateTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateTimeToStringConversion.java index eda7473fbb..4a2804eba0 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateToStringConversion.java index 13f984760a..7201a6c65a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalDateToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalTimeToStringConversion.java index 9bbdfaeb15..2305f840f1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaLocalTimeToStringConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 +/* + * Copyright MapStruct Authors. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaTimeToDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaTimeToDateConversion.java index 086c119675..d8212e140e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaTimeToDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JodaTimeToDateConversion.java @@ -1,31 +1,18 @@ -/** - * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) - * and/or other contributors as indicated by the @authors tag. See the - * copyright.txt file in the distribution for a full listing of all - * contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/* + * Copyright MapStruct Authors. * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.util.Date; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between the following Joda types and {@link Date}: *